Util.java revision 990
0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License (the "License").
0N/A * You may not use this file except in compliance with the License.
0N/A *
0N/A * See LICENSE.txt included in this distribution for the specific
0N/A * language governing permissions and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at LICENSE.txt.
0N/A * If applicable, add the following below this CDDL HEADER, with the
0N/A * fields enclosed by brackets "[]" replaced with your own identifying
0N/A * information: Portions Copyright [yyyy] [name of copyright owner]
0N/A *
0N/A * CDDL HEADER END
0N/A */
0N/A
0N/A/*
0N/A * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
0N/A * Use is subject to license terms.
0N/A */
0N/Apackage org.opensolaris.opengrok.web;
0N/A
0N/Aimport java.io.IOException;
0N/Aimport java.io.UnsupportedEncodingException;
0N/Aimport java.io.Writer;
0N/Aimport java.net.URI;
0N/Aimport java.net.URISyntaxException;
0N/Aimport java.net.URLEncoder;
0N/Aimport java.text.DecimalFormat;
0N/Aimport java.text.NumberFormat;
0N/Aimport java.util.ArrayList;
0N/Aimport java.util.logging.Level;
0N/Aimport org.opensolaris.opengrok.OpenGrokLogger;
0N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
0N/Aimport org.opensolaris.opengrok.history.Annotation;
0N/A
0N/A/**
0N/A * File for useful functions
0N/A */
0N/Apublic final class Util {
0N/A /**
0N/A * Return a string which represents a <code>CharSequence</code> in HTML.
0N/A *
0N/A * @param q a character sequence
0N/A * @return a string representing the character sequence in HTML
0N/A */
0N/A
0N/A private Util() {
0N/A // Util class, should not be constructed
0N/A }
0N/A
0N/A public static String htmlize(CharSequence q) {
0N/A StringBuilder sb = new StringBuilder(q.length() * 2);
0N/A htmlize(q, sb);
0N/A return sb.toString();
0N/A }
0N/A
0N/A /**
0N/A * Append a character sequence to an <code>Appendable</code> object. Escape
0N/A * special characters for HTML.
0N/A *
0N/A * @param q a character sequence
0N/A * @param out the object to append the character sequence to
0N/A * @exception IOException if an I/O error occurs
0N/A */
0N/A public static void htmlize(CharSequence q, Appendable out)
0N/A throws IOException {
0N/A for (int i = 0; i < q.length(); i++) {
0N/A htmlize(q.charAt(i), out);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Append a character sequence to a <code>StringBuilder</code>
0N/A * object. Escape special characters for HTML. This method is identical to
0N/A * <code>htmlize(CharSequence,Appendable)</code>, except that it is
0N/A * guaranteed not to throw <code>IOException</code> because it uses a
0N/A * <code>StringBuilder</code>.
0N/A *
0N/A * @param q a character sequence
0N/A * @param out the object to append the character sequence to
0N/A * @see #htmlize(CharSequence, Appendable)
0N/A */
0N/A @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
0N/A public static void htmlize(CharSequence q, StringBuilder out) {
0N/A try {
0N/A htmlize(q, (Appendable) out);
0N/A } catch (IOException ioe) {
0N/A // StringBuilder's append methods are not declared to throw
0N/A // IOException, so this should never happen.
0N/A throw new RuntimeException("StringBuilder should not throw IOException", ioe);
0N/A }
0N/A }
0N/A
0N/A public static void htmlize(char[] cs, int length, Appendable out)
0N/A throws IOException {
0N/A for (int i = 0; i < length && i < cs.length; i++) {
0N/A htmlize(cs[i], out);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Append a character to a an <code>Appendable</code> object. If the
0N/A * character has special meaning in HTML, append a sequence of characters
0N/A * representing the special character.
0N/A *
0N/A * @param c the character to append
0N/A * @param out the object to append the character to
0N/A * @exception IOException if an I/O error occurs
0N/A */
0N/A private static void htmlize(char c, Appendable out) throws IOException {
0N/A switch (c) {
0N/A case '&':
0N/A out.append("&amp;");
0N/A break;
0N/A case '>':
0N/A out.append("&gt;");
0N/A break;
0N/A case '<':
0N/A out.append("&lt;");
0N/A break;
0N/A case '\n':
0N/A out.append("<br/>");
0N/A break;
0N/A default:
0N/A out.append(c);
0N/A }
0N/A }
0N/A
0N/A private static String versionP=htmlize(org.opensolaris.opengrok.Info.getRevision());
0N/A /**
0N/A * used by BUI - CSS needs this parameter for proper cache refresh (per changeset) in client browser
0N/A * @return html escaped version (hg changeset)
0N/A */
0N/A public static String versionParameter() {
0N/A return versionP;
0N/A }
0N/A
0N/A /**
0N/A * Same as {@code breadcrumbPath(urlPrefix, l, '/')}.
0N/A * @see #breadcrumbPath(String, String, char)
0N/A */
0N/A public static String breadcrumbPath(String urlPrefix, String l) {
0N/A return breadcrumbPath(urlPrefix, l, '/');
0N/A }
0N/A
0N/A private static final String anchorLinkStart = "<a href=\"";
0N/A private static final String anchorClassStart = "<a class=\"";
0N/A private static final String anchorEnd = "</a>";
0N/A private static final String closeQuotedTag = "\">";
0N/A
0N/A /**
0N/A * Same as {@code breadcrumbPath(urlPrefix, l, sep, "", false)}.
0N/A * @see #breadcrumbPath(String, String, char, String, boolean)
0N/A */
0N/A public static String breadcrumbPath(String urlPrefix, String l, char sep) {
return breadcrumbPath(urlPrefix, l, sep, "", false);
}
/**
* Create a breadcrumb path to allow navigation to each element of a path.
*
* @param urlPrefix what comes before the path in the URL
* @param l the full path from which the breadcrumb path is built
* @param sep the character that separates the path elements in {@code l}
* @param urlPostfix what comes after the path in the URL
* @param compact if {@code true}, remove {@code ..} and empty path
* elements from the path in the links
* @return HTML markup for the breadcrumb path
*/
public static String breadcrumbPath(
String urlPrefix, String l, char sep, String urlPostfix,
boolean compact) {
if (l == null || l.length() <= 1) {
return l;
}
StringBuilder hyperl = new StringBuilder(20);
String[] path = l.split(escapeForRegex(sep), -1);
for (int i = 0; i < path.length; i++) {
leaveBreadcrumb(
urlPrefix, sep, urlPostfix, compact, hyperl, path, i);
}
return hyperl.toString();
}
/**
* Leave a breadcrumb to allow navigation to one of the parent directories.
* Write a hyperlink to the specified {@code StringBuilder}.
*
* @param urlPrefix what comes before the path in the URL
* @param sep the character that separates path elements
* @param urlPostfix what comes after the path in the URL
* @param compact if {@code true}, remove {@code ..} and empty path
* elements from the path in the link
* @param hyperl a string builder to which the hyperlink is written
* @param path all the elements of the full path
* @param index which path element to create a link to
*/
private static void leaveBreadcrumb(
String urlPrefix, char sep, String urlPostfix, boolean compact,
StringBuilder hyperl, String[] path, int index) {
// Only generate the link if the path element is non-empty. Empty
// path elements could occur if the path contains two consecutive
// separator characters, or if the path begins or ends with a path
// separator.
if (path[index].length() > 0) {
hyperl.append(anchorLinkStart).append(urlPrefix);
appendPath(path, index, hyperl, compact);
hyperl.append(urlPostfix).append(closeQuotedTag).
append(path[index]).append(anchorEnd);
}
// Add a separator between each path element, but not after the last
// one. If the original path ended with a separator, the last element
// of the path array is an empty string, which means that the final
// separator will be printed.
if (index < path.length - 1) {
hyperl.append(sep);
}
}
/**
* Append parts of a file path to a {@code StringBuilder}. Separate each
* element in the path with "/". The path elements from index 0 up to
* index {@code lastIndex} (inclusive) are used.
*
* @param path array of path elements
* @param lastIndex the index of the last path element to use
* @param out the {@code StringBuilder} to which the path is appended
* @param compact if {@code true}, remove {@code ..} and empty path
* elements from the path in the link
*/
private static void appendPath(
String[] path, int lastIndex, StringBuilder out, boolean compact) {
final ArrayList<String> elements = new ArrayList<String>(lastIndex + 1);
// Copy the relevant part of the path. If compact is false, just
// copy the lastIndex first elements. If compact is true, remove empty
// path elements, and follow .. up to the parent directory. Occurrences
// of .. at the beginning of the path will be removed.
for (int i = 0; i <= lastIndex; i++) {
if (compact) {
if ("..".equals(path[i])) {
if (!elements.isEmpty()) {
elements.remove(elements.size() - 1);
}
} else if (!"".equals(path[i])) {
elements.add(path[i]);
}
} else {
elements.add(path[i]);
}
}
// Print the path with / between each element. No separator before
// the first element or after the last element.
for (int i = 0; i < elements.size(); i++) {
out.append(elements.get(i));
if (i < elements.size() - 1) {
out.append("/");
}
}
}
/**
* Generate a regex that matches the specified character. Escape it in
* case it is a character that has a special meaning in a regex.
*
* @param c the character that the regex should match
* @return a six-character string on the form <tt>&#92;u</tt><i>hhhh</i>
*/
private static String escapeForRegex(char c) {
StringBuilder sb = new StringBuilder(6);
sb.append("\\u");
String hex = Integer.toHexString((int) c);
for (int i = 0; i < 4 - hex.length(); i++) {
sb.append('0');
}
sb.append(hex);
return sb.toString();
}
public static String redableSize(long num) {
float l = (float) num;
NumberFormat formatter = new DecimalFormat("#,###,###,###.#");
if (l < 1024) {
return formatter.format(l);
} else if (l < 1048576) {
return (formatter.format(l / 1024) + "K");
} else {
return ("<b>" + formatter.format(l / 1048576) + "M</b>");
}
}
/**
* Converts different html special characters into their encodings used in html
* currently used only for tooltips of annotation revision number view
* @param s input text
* @return encoded text for use in <a title=""> tag
*/
public static String encode(String s) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '"':
sb.append('\'');
break; // \\\"
case '&':
sb.append("&amp;");
break;
case '>':
sb.append("&gt;");
break;
case '<':
sb.append("&lt;");
break;
case ' ':
sb.append("&nbsp;");
break;
case '\t':
sb.append("&nbsp;&nbsp;&nbsp;&nbsp;");
break;
case '\n':
sb.append("<br/>");
break;
case '\r':
break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
public static void readableLine(int num, Writer out, Annotation annotation)
throws IOException {
String snum = String.valueOf(num);
if (num > 1) {
out.write("\n");
}
out.write(anchorClassStart);
out.write((num % 10 == 0 ? "hl" : "l"));
out.write("\" name=\"");
out.write(snum);
out.write("\" href=\"#");
out.write(snum);
out.write(closeQuotedTag);
out.write((num > 999 ? "&nbsp;&nbsp;&nbsp;" : (num > 99 ? "&nbsp;&nbsp;&nbsp;&nbsp;" : (num > 9 ? "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" : "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"))));
out.write(snum);
out.write("&nbsp;");
out.write(anchorEnd);
if (annotation != null) {
String r = annotation.getRevision(num);
boolean enabled = annotation.isEnabled(num);
out.write("<span class=\"blame\"><span class=\"l\"> ");
for (int i = r.length(); i < annotation.getWidestRevision(); i++) {
out.write(" ");
}
if (enabled) {
out.write(anchorLinkStart);
out.write(URIEncode(annotation.getFilename()));
out.write("?a=true&amp;r=");
out.write(URIEncode(r));
String msg=annotation.getDesc(r);
if (msg!=null) {
out.write("\" id=\"r\" title=\""+msg+"\"");
}
out.write(closeQuotedTag);
}
htmlize(r, out);
if (enabled) {
out.write(anchorEnd);
}
out.write(" </span>");
String a = annotation.getAuthor(num);
out.write("<span class=\"l\"> ");
for (int i = a.length(); i < annotation.getWidestAuthor(); i++) {
out.write(" ");
}
String link = RuntimeEnvironment.getInstance().getUserPage();
if (link != null && link.length() > 0) {
out.write(anchorLinkStart);
out.write(link);
out.write(URIEncode(a));
out.write(closeQuotedTag);
htmlize(a, out);
out.write(anchorEnd);
} else {
htmlize(a, out);
}
out.write(" </span></span>");
}
}
/**
* Append path and date into a string in such a way that lexicographic
* sorting gives the same results as a walk of the file hierarchy. Thus
* null (\u0000) is used both to separate directory components and to
* separate the path from the date.
*/
public static String uid(String path, String date) {
return path.replace('/', '\u0000') + "\u0000" + date;
}
public static String uid2url(String uid) {
String url = uid.replace('\u0000', '/'); // replace nulls with slashes
return url.substring(0, url.lastIndexOf('/')); // remove date from end
}
/**
* wrapper arround UTF-8 URL encoding of a string
* @param q query to be encoded
* @return null if fail, otherwise the encoded string
*/
public static String URIEncode(String q) {
try {
return URLEncoder.encode(q, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Should not happen. UTF-8 must be supported by JVMs.
return null;
}
}
public static String URIEncodePath(String path) {
try {
URI uri = new URI(null, null, path, null);
return uri.getRawPath();
} catch (URISyntaxException ex) {
OpenGrokLogger.getLogger().log(Level.WARNING, "Could not encode path " + path, ex);
return "";
}
}
public static String formQuoteEscape(String q) {
if (q == null) {
return "";
}
StringBuilder sb = new StringBuilder();
char c;
for (int i = 0; i < q.length(); i++) {
c = q.charAt(i);
if (c == '"') {
sb.append("&quot;");
} else {
sb.append(c);
}
}
return sb.toString();
}
}