Util.java revision 1124
0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
407N/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/*
1124N/A * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A */
0N/Apackage org.opensolaris.opengrok.web;
0N/A
154N/Aimport java.io.IOException;
159N/Aimport java.io.UnsupportedEncodingException;
154N/Aimport java.io.Writer;
292N/Aimport java.net.URI;
292N/Aimport java.net.URISyntaxException;
159N/Aimport java.net.URLEncoder;
154N/Aimport java.text.DecimalFormat;
154N/Aimport java.text.NumberFormat;
605N/Aimport java.util.ArrayList;
1124N/Aimport java.util.Collection;
434N/Aimport java.util.logging.Level;
434N/Aimport org.opensolaris.opengrok.OpenGrokLogger;
259N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
89N/Aimport org.opensolaris.opengrok.history.Annotation;
1124N/Aimport org.opensolaris.opengrok.history.HistoryException;
1124N/Aimport org.opensolaris.opengrok.history.HistoryGuru;
0N/A
0N/A/**
0N/A * File for useful functions
0N/A */
456N/Apublic final class Util {
92N/A /**
92N/A * Return a string which represents a <code>CharSequence</code> in HTML.
92N/A *
92N/A * @param q a character sequence
92N/A * @return a string representing the character sequence in HTML
92N/A */
154N/A
456N/A private Util() {
456N/A // Util class, should not be constructed
456N/A }
456N/A
345N/A public static String htmlize(CharSequence q) {
0N/A StringBuilder sb = new StringBuilder(q.length() * 2);
345N/A htmlize(q, sb);
0N/A return sb.toString();
0N/A }
92N/A
92N/A /**
92N/A * Append a character sequence to an <code>Appendable</code> object. Escape
92N/A * special characters for HTML.
92N/A *
92N/A * @param q a character sequence
92N/A * @param out the object to append the character sequence to
92N/A * @exception IOException if an I/O error occurs
92N/A */
345N/A public static void htmlize(CharSequence q, Appendable out)
92N/A throws IOException {
92N/A for (int i = 0; i < q.length(); i++) {
345N/A htmlize(q.charAt(i), out);
0N/A }
0N/A }
92N/A
92N/A /**
92N/A * Append a character sequence to a <code>StringBuilder</code>
92N/A * object. Escape special characters for HTML. This method is identical to
345N/A * <code>htmlize(CharSequence,Appendable)</code>, except that it is
92N/A * guaranteed not to throw <code>IOException</code> because it uses a
92N/A * <code>StringBuilder</code>.
92N/A *
92N/A * @param q a character sequence
92N/A * @param out the object to append the character sequence to
345N/A * @see #htmlize(CharSequence, Appendable)
92N/A */
439N/A @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
345N/A public static void htmlize(CharSequence q, StringBuilder out) {
92N/A try {
345N/A htmlize(q, (Appendable) out);
92N/A } catch (IOException ioe) {
92N/A // StringBuilder's append methods are not declared to throw
92N/A // IOException, so this should never happen.
154N/A throw new RuntimeException("StringBuilder should not throw IOException", ioe);
92N/A }
92N/A }
92N/A
345N/A public static void htmlize(char[] cs, int length, Appendable out)
92N/A throws IOException {
154N/A for (int i = 0; i < length && i < cs.length; i++) {
345N/A htmlize(cs[i], out);
92N/A }
92N/A }
92N/A
92N/A /**
92N/A * Append a character to a an <code>Appendable</code> object. If the
92N/A * character has special meaning in HTML, append a sequence of characters
92N/A * representing the special character.
92N/A *
92N/A * @param c the character to append
92N/A * @param out the object to append the character to
92N/A * @exception IOException if an I/O error occurs
92N/A */
345N/A private static void htmlize(char c, Appendable out) throws IOException {
92N/A switch (c) {
154N/A case '&':
154N/A out.append("&amp;");
154N/A break;
154N/A case '>':
154N/A out.append("&gt;");
154N/A break;
154N/A case '<':
154N/A out.append("&lt;");
154N/A break;
154N/A case '\n':
154N/A out.append("<br/>");
154N/A break;
154N/A default:
154N/A out.append(c);
92N/A }
92N/A }
92N/A
990N/A private static String versionP=htmlize(org.opensolaris.opengrok.Info.getRevision());
990N/A /**
990N/A * used by BUI - CSS needs this parameter for proper cache refresh (per changeset) in client browser
990N/A * @return html escaped version (hg changeset)
990N/A */
990N/A public static String versionParameter() {
990N/A return versionP;
990N/A }
990N/A
604N/A /**
604N/A * Same as {@code breadcrumbPath(urlPrefix, l, '/')}.
604N/A * @see #breadcrumbPath(String, String, char)
604N/A */
0N/A public static String breadcrumbPath(String urlPrefix, String l) {
0N/A return breadcrumbPath(urlPrefix, l, '/');
0N/A }
154N/A
583N/A private static final String anchorLinkStart = "<a href=\"";
583N/A private static final String anchorClassStart = "<a class=\"";
583N/A private static final String anchorEnd = "</a>";
583N/A private static final String closeQuotedTag = "\">";
604N/A
604N/A /**
605N/A * Same as {@code breadcrumbPath(urlPrefix, l, sep, "", false)}.
605N/A * @see #breadcrumbPath(String, String, char, String, boolean)
604N/A */
0N/A public static String breadcrumbPath(String urlPrefix, String l, char sep) {
605N/A return breadcrumbPath(urlPrefix, l, sep, "", false);
604N/A }
604N/A
604N/A /**
604N/A * Create a breadcrumb path to allow navigation to each element of a path.
604N/A *
604N/A * @param urlPrefix what comes before the path in the URL
604N/A * @param l the full path from which the breadcrumb path is built
604N/A * @param sep the character that separates the path elements in {@code l}
604N/A * @param urlPostfix what comes after the path in the URL
605N/A * @param compact if {@code true}, remove {@code ..} and empty path
605N/A * elements from the path in the links
604N/A * @return HTML markup for the breadcrumb path
604N/A */
604N/A public static String breadcrumbPath(
605N/A String urlPrefix, String l, char sep, String urlPostfix,
605N/A boolean compact) {
154N/A if (l == null || l.length() <= 1) {
0N/A return l;
154N/A }
0N/A StringBuilder hyperl = new StringBuilder(20);
604N/A String[] path = l.split(escapeForRegex(sep), -1);
604N/A for (int i = 0; i < path.length; i++) {
605N/A leaveBreadcrumb(
605N/A urlPrefix, sep, urlPostfix, compact, hyperl, path, i);
604N/A }
604N/A return hyperl.toString();
604N/A }
604N/A
604N/A /**
604N/A * Leave a breadcrumb to allow navigation to one of the parent directories.
604N/A * Write a hyperlink to the specified {@code StringBuilder}.
604N/A *
604N/A * @param urlPrefix what comes before the path in the URL
604N/A * @param sep the character that separates path elements
604N/A * @param urlPostfix what comes after the path in the URL
605N/A * @param compact if {@code true}, remove {@code ..} and empty path
605N/A * elements from the path in the link
604N/A * @param hyperl a string builder to which the hyperlink is written
604N/A * @param path all the elements of the full path
604N/A * @param index which path element to create a link to
604N/A */
604N/A private static void leaveBreadcrumb(
605N/A String urlPrefix, char sep, String urlPostfix, boolean compact,
605N/A StringBuilder hyperl, String[] path, int index) {
604N/A // Only generate the link if the path element is non-empty. Empty
604N/A // path elements could occur if the path contains two consecutive
604N/A // separator characters, or if the path begins or ends with a path
604N/A // separator.
604N/A if (path[index].length() > 0) {
604N/A hyperl.append(anchorLinkStart).append(urlPrefix);
605N/A appendPath(path, index, hyperl, compact);
604N/A hyperl.append(urlPostfix).append(closeQuotedTag).
604N/A append(path[index]).append(anchorEnd);
604N/A }
604N/A // Add a separator between each path element, but not after the last
604N/A // one. If the original path ended with a separator, the last element
604N/A // of the path array is an empty string, which means that the final
604N/A // separator will be printed.
604N/A if (index < path.length - 1) {
0N/A hyperl.append(sep);
0N/A }
604N/A }
604N/A
604N/A /**
604N/A * Append parts of a file path to a {@code StringBuilder}. Separate each
604N/A * element in the path with "/". The path elements from index 0 up to
604N/A * index {@code lastIndex} (inclusive) are used.
604N/A *
604N/A * @param path array of path elements
604N/A * @param lastIndex the index of the last path element to use
604N/A * @param out the {@code StringBuilder} to which the path is appended
605N/A * @param compact if {@code true}, remove {@code ..} and empty path
605N/A * elements from the path in the link
604N/A */
604N/A private static void appendPath(
605N/A String[] path, int lastIndex, StringBuilder out, boolean compact) {
605N/A final ArrayList<String> elements = new ArrayList<String>(lastIndex + 1);
605N/A
605N/A // Copy the relevant part of the path. If compact is false, just
605N/A // copy the lastIndex first elements. If compact is true, remove empty
605N/A // path elements, and follow .. up to the parent directory. Occurrences
605N/A // of .. at the beginning of the path will be removed.
604N/A for (int i = 0; i <= lastIndex; i++) {
605N/A if (compact) {
605N/A if ("..".equals(path[i])) {
605N/A if (!elements.isEmpty()) {
605N/A elements.remove(elements.size() - 1);
605N/A }
605N/A } else if (!"".equals(path[i])) {
605N/A elements.add(path[i]);
605N/A }
605N/A } else {
605N/A elements.add(path[i]);
605N/A }
605N/A }
605N/A
605N/A // Print the path with / between each element. No separator before
605N/A // the first element or after the last element.
605N/A for (int i = 0; i < elements.size(); i++) {
605N/A out.append(elements.get(i));
605N/A if (i < elements.size() - 1) {
604N/A out.append("/");
0N/A }
0N/A }
604N/A }
604N/A
604N/A /**
604N/A * Generate a regex that matches the specified character. Escape it in
604N/A * case it is a character that has a special meaning in a regex.
604N/A *
604N/A * @param c the character that the regex should match
604N/A * @return a six-character string on the form <tt>&#92;u</tt><i>hhhh</i>
604N/A */
604N/A private static String escapeForRegex(char c) {
604N/A StringBuilder sb = new StringBuilder(6);
604N/A sb.append("\\u");
604N/A String hex = Integer.toHexString((int) c);
604N/A for (int i = 0; i < 4 - hex.length(); i++) {
604N/A sb.append('0');
0N/A }
604N/A sb.append(hex);
604N/A return sb.toString();
0N/A }
154N/A
0N/A public static String redableSize(long num) {
0N/A float l = (float) num;
0N/A NumberFormat formatter = new DecimalFormat("#,###,###,###.#");
154N/A if (l < 1024) {
0N/A return formatter.format(l);
154N/A } else if (l < 1048576) {
0N/A return (formatter.format(l / 1024) + "K");
0N/A } else {
154N/A return ("<b>" + formatter.format(l / 1048576) + "M</b>");
0N/A }
0N/A }
154N/A
878N/A /**
878N/A * Converts different html special characters into their encodings used in html
878N/A * currently used only for tooltips of annotation revision number view
878N/A * @param s input text
878N/A * @return encoded text for use in <a title=""> tag
878N/A */
878N/A public static String encode(String s) {
972N/A StringBuffer sb = new StringBuffer();
972N/A for (int i = 0; i < s.length(); i++) {
972N/A char c = s.charAt(i);
878N/A
972N/A switch (c) {
972N/A case '"':
972N/A sb.append('\'');
972N/A break; // \\\"
972N/A case '&':
972N/A sb.append("&amp;");
972N/A break;
972N/A case '>':
972N/A sb.append("&gt;");
972N/A break;
972N/A case '<':
972N/A sb.append("&lt;");
972N/A break;
972N/A case ' ':
972N/A sb.append("&nbsp;");
972N/A break;
972N/A case '\t':
972N/A sb.append("&nbsp;&nbsp;&nbsp;&nbsp;");
972N/A break;
972N/A case '\n':
972N/A sb.append("<br/>");
972N/A break;
972N/A case '\r':
972N/A break;
972N/A default:
972N/A sb.append(c);
972N/A break;
972N/A }
972N/A }
878N/A
972N/A return sb.toString();
972N/A }
878N/A
89N/A public static void readableLine(int num, Writer out, Annotation annotation)
89N/A throws IOException {
0N/A String snum = String.valueOf(num);
54N/A if (num > 1) {
54N/A out.write("\n");
54N/A }
583N/A out.write(anchorClassStart);
0N/A out.write((num % 10 == 0 ? "hl" : "l"));
0N/A out.write("\" name=\"");
0N/A out.write(snum);
847N/A out.write("\" href=\"#");
847N/A out.write(snum);
583N/A out.write(closeQuotedTag);
847N/A 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;"))));
0N/A out.write(snum);
847N/A out.write("&nbsp;");
583N/A out.write(anchorEnd);
89N/A if (annotation != null) {
89N/A String r = annotation.getRevision(num);
168N/A boolean enabled = annotation.isEnabled(num);
168N/A
181N/A out.write("<span class=\"blame\"><span class=\"l\"> ");
89N/A for (int i = r.length(); i < annotation.getWidestRevision(); i++) {
89N/A out.write(" ");
89N/A }
154N/A
168N/A if (enabled) {
583N/A out.write(anchorLinkStart);
168N/A out.write(URIEncode(annotation.getFilename()));
850N/A out.write("?a=true&amp;r=");
168N/A out.write(URIEncode(r));
878N/A String msg=annotation.getDesc(r);
878N/A if (msg!=null) {
878N/A out.write("\" id=\"r\" title=\""+msg+"\"");
878N/A }
583N/A out.write(closeQuotedTag);
168N/A }
168N/A
345N/A htmlize(r, out);
168N/A
168N/A if (enabled) {
583N/A out.write(anchorEnd);
168N/A }
168N/A
89N/A out.write(" </span>");
89N/A
89N/A String a = annotation.getAuthor(num);
89N/A out.write("<span class=\"l\"> ");
89N/A for (int i = a.length(); i < annotation.getWidestAuthor(); i++) {
89N/A out.write(" ");
89N/A }
259N/A String link = RuntimeEnvironment.getInstance().getUserPage();
1100N/A String suffix = RuntimeEnvironment.getInstance().getUserPageSuffix();
259N/A if (link != null && link.length() > 0) {
583N/A out.write(anchorLinkStart);
259N/A out.write(link);
259N/A out.write(URIEncode(a));
1100N/A if (suffix != null && 0 < suffix.length()) {
1100N/A out.write(suffix);
1100N/A }
583N/A out.write(closeQuotedTag);
345N/A htmlize(a, out);
583N/A out.write(anchorEnd);
259N/A } else {
345N/A htmlize(a, out);
259N/A }
181N/A out.write(" </span></span>");
89N/A }
0N/A }
154N/A
0N/A /**
0N/A * Append path and date into a string in such a way that lexicographic
0N/A * sorting gives the same results as a walk of the file hierarchy. Thus
0N/A * null (\u0000) is used both to separate directory components and to
0N/A * separate the path from the date.
0N/A */
0N/A public static String uid(String path, String date) {
153N/A return path.replace('/', '\u0000') + "\u0000" + date;
0N/A }
154N/A
0N/A public static String uid2url(String uid) {
0N/A String url = uid.replace('\u0000', '/'); // replace nulls with slashes
0N/A return url.substring(0, url.lastIndexOf('/')); // remove date from end
0N/A }
154N/A
849N/A /**
849N/A * wrapper arround UTF-8 URL encoding of a string
850N/A * @param q query to be encoded
849N/A * @return null if fail, otherwise the encoded string
849N/A */
0N/A public static String URIEncode(String q) {
830N/A try {
830N/A return URLEncoder.encode(q, "UTF-8");
159N/A } catch (UnsupportedEncodingException e) {
830N/A // Should not happen. UTF-8 must be supported by JVMs.
159N/A return null;
0N/A }
0N/A }
154N/A
158N/A public static String URIEncodePath(String path) {
292N/A try {
292N/A URI uri = new URI(null, null, path, null);
292N/A return uri.getRawPath();
292N/A } catch (URISyntaxException ex) {
434N/A OpenGrokLogger.getLogger().log(Level.WARNING, "Could not encode path " + path, ex);
292N/A return "";
158N/A }
158N/A }
158N/A
0N/A public static String formQuoteEscape(String q) {
58N/A if (q == null) {
58N/A return "";
58N/A }
0N/A StringBuilder sb = new StringBuilder();
0N/A char c;
154N/A for (int i = 0; i < q.length(); i++) {
0N/A c = q.charAt(i);
154N/A if (c == '"') {
0N/A sb.append("&quot;");
0N/A } else {
0N/A sb.append(c);
0N/A }
0N/A }
0N/A return sb.toString();
0N/A }
1025N/A
1025N/A /**
1025N/A * Highlight the diffs between line1 and line2
1025N/A * @param line1
1025N/A * @param line2
1025N/A * @return new strings with html tags highlighting the diffs
1025N/A */
1025N/A public static String[] diffline(String line1, String line2) {
1025N/A String[] ret = new String[2];
1025N/A int s=0;
1025N/A int m=line1.length()-1;
1025N/A int n=line2.length()-1;
1025N/A while (s <= m && s <= n && (line1.charAt(s) == line2.charAt(s)))
1025N/A { s++; }
1025N/A
1025N/A while (s <= m && s <= n && (line1.charAt(m) == line2.charAt(n)))
1025N/A { m--;n--; }
1025N/A
1025N/A StringBuilder sb = new StringBuilder(line1);
1025N/A String spand="<span class=\"d\">";
1025N/A if(s <= m) {
1025N/A sb.insert(s, spand);
1025N/A sb.insert(spand.length()+m+1, "</span>");
1025N/A ret[0] = sb.toString();
1025N/A } else {
1025N/A ret[0] = line1;
1025N/A }
1025N/A String spana="<span class=\"a\">";
1025N/A if(s <= n) {
1025N/A sb = new StringBuilder(line2);
1025N/A sb.insert(s, spana);
1025N/A sb.insert(spana.length()+n+1, "</span>");
1025N/A ret[1] = sb.toString();
1025N/A } else {
1025N/A ret[1] = line2;
1025N/A }
1025N/A return ret;
1025N/A
1025N/A }
1124N/A
1124N/A /**
1124N/A * Dump the configuration as an HTML table.
1124N/A *
1124N/A * @param out destination for the HTML output
1124N/A * @throws IOException if an error happens while writing to {@code out}
1124N/A * @throws HistoryException if the history guru cannot be accesses
1124N/A */
1124N/A public static void dumpConfiguration(Appendable out)
1124N/A throws IOException, HistoryException {
1124N/A out.append("<table border=\"1\" width=\"100%\">");
1124N/A out.append("<tr><th>Variable</th><th>Value</th></tr>");
1124N/A
1124N/A RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1124N/A
1124N/A printTableRow(out, "Source root", env.getSourceRootPath());
1124N/A printTableRow(out, "Data root", env.getDataRootPath());
1124N/A printTableRow(out, "CTags", env.getCtags());
1124N/A printTableRow(out, "Bug page", env.getBugPage());
1124N/A printTableRow(out, "Bug pattern", env.getBugPattern());
1124N/A printTableRow(out, "User page", env.getUserPage());
1124N/A printTableRow(out, "Review page", env.getReviewPage());
1124N/A printTableRow(out, "Review pattern", env.getReviewPattern());
1124N/A printTableRow(out, "Using projects", env.hasProjects());
1124N/A
1124N/A out.append("<tr><td>Ignored files</td><td>");
1124N/A printUnorderedList(out, env.getIgnoredNames().getItems());
1124N/A out.append("</td></tr>");
1124N/A
1124N/A printTableRow(out, "Index word limit", env.getIndexWordLimit());
1124N/A printTableRow(out, "Allow leading wildcard in search",
1124N/A env.isAllowLeadingWildcard());
1124N/A printTableRow(out, "History cache",
1124N/A HistoryGuru.getInstance().getCacheInfo());
1124N/A
1124N/A out.append("</table>");
1124N/A }
1124N/A
1124N/A /**
1124N/A * Print a row in an HTML table.
1124N/A *
1124N/A * @param out destination for the HTML output
1124N/A * @param cells the values to print in the cells of the row
1124N/A * @throws IOException if an error happens while writing to {@code out}
1124N/A */
1124N/A private static void printTableRow(Appendable out, Object... cells)
1124N/A throws IOException {
1124N/A out.append("<tr>");
1124N/A for (Object cell : cells) {
1124N/A out.append("<td>");
1124N/A String str = (cell == null) ? "null" : cell.toString();
1124N/A htmlize(str, out);
1124N/A out.append("</td>");
1124N/A }
1124N/A out.append("</tr>");
1124N/A }
1124N/A
1124N/A /**
1124N/A * Print an unordered list (HTML).
1124N/A *
1124N/A * @param out destination for the HTML output
1124N/A * @param items the list items
1124N/A * @throws IOException if an error happens while writing to {@code out}
1124N/A */
1124N/A private static void printUnorderedList(
1124N/A Appendable out, Collection<String> items) throws IOException {
1124N/A out.append("<ul>");
1124N/A for (String item : items) {
1124N/A out.append("<li>");
1124N/A htmlize(item, out);
1124N/A out.append("</li>");
1124N/A }
1124N/A out.append("</ul>");
1124N/A }
0N/A}