Util.java revision 1469
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 */
1220N/A
0N/A/*
1297N/A * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
1356N/A * Portions Copyright 2011, 2012 Jens Elkner.
0N/A */
0N/Apackage org.opensolaris.opengrok.web;
0N/A
1185N/Aimport java.io.File;
1185N/Aimport java.io.FileInputStream;
1185N/Aimport java.io.FileReader;
154N/Aimport java.io.IOException;
1185N/Aimport java.io.InputStreamReader;
1185N/Aimport java.io.Reader;
154N/Aimport java.io.Writer;
1366N/Aimport java.nio.ByteBuffer;
1366N/Aimport java.nio.CharBuffer;
1376N/Aimport java.nio.charset.Charset;
154N/Aimport java.text.DecimalFormat;
154N/Aimport java.text.NumberFormat;
1124N/Aimport java.util.Collection;
1330N/Aimport java.util.Date;
1384N/Aimport java.util.Iterator;
1185N/Aimport java.util.LinkedList;
434N/Aimport java.util.logging.Level;
1219N/Aimport java.util.logging.Logger;
1297N/Aimport java.util.regex.Matcher;
1297N/Aimport java.util.regex.Pattern;
1185N/Aimport java.util.zip.GZIPInputStream;
1185N/A
1185N/Aimport org.opensolaris.opengrok.Info;
259N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
1124N/Aimport org.opensolaris.opengrok.history.HistoryException;
1124N/Aimport org.opensolaris.opengrok.history.HistoryGuru;
1195N/Aimport org.opensolaris.opengrok.util.IOUtils;
0N/A
0N/A/**
1185N/A * Class for useful functions.
0N/A */
456N/Apublic final class Util {
1327N/A private static final Logger logger = Logger.getLogger(Util.class.getName());
1185N/A private Util() {
1185N/A // singleton
1185N/A }
1185N/A
92N/A /**
1469N/A * Replace all HTML special characters '&', '<', '>' in the given String
1469N/A * with their corresponding HTML entity references.
1469N/A * @param s string to htmlize
1469N/A * @return <var>s</var> if it doesn't contain a special character, a new
1469N/A * string otherwise.
92N/A */
1469N/A public static String htmlize(String s) {
1469N/A int start = -1;
1469N/A for (int i=0; i < s.length(); i++) {
1469N/A char c = s.charAt(i);
1469N/A if (c == '>' || c == '<' || c == '&') {
1469N/A start = i;
1469N/A break;
1469N/A }
1469N/A }
1469N/A if (start == -1) {
1469N/A return s;
1469N/A }
1469N/A StringBuilder sb = new StringBuilder(s.substring(0, start));
1469N/A sb.ensureCapacity(s.length() + 8);
1469N/A // since we can't copy sequences efficiently we copy char by char :(
1469N/A for (int i=start; i < s.length(); i++) {
1469N/A char c = s.charAt(i);
1469N/A switch (c) {
1469N/A case '&':
1469N/A sb.append("&amp;");
1469N/A break;
1469N/A case '>':
1469N/A sb.append("&gt;");
1469N/A break;
1469N/A case '<':
1469N/A sb.append("&lt;");
1469N/A break;
1469N/A default:
1469N/A sb.append(c);
1469N/A }
1469N/A }
0N/A return sb.toString();
0N/A }
92N/A
92N/A /**
1469N/A * Replace all HTML special characters '&', '<', '>' AND linefeeds ('\n')
1469N/A * in the given String with their corresponding HTML entity references.
1469N/A * @param s string to htmlize
1469N/A * @param eol the replacement to use for linefeeds ('\n')
1469N/A * @return <var>s</var> if it doesn't contain a special character, a new
1469N/A * string otherwise.
92N/A */
1469N/A public static String htmlize(String s, String eol) {
1469N/A if (eol == null) {
1469N/A eol = "\\n";
1185N/A }
1469N/A int start = -1;
1469N/A for (int i=0; i < s.length(); i++) {
1469N/A char c = s.charAt(i);
1469N/A if (c == '>' || c == '<' || c == '&' || c == '\n') {
1469N/A start = i;
1469N/A break;
1469N/A }
1469N/A }
1469N/A if (start == -1) {
1469N/A return s;
1185N/A }
1469N/A StringBuilder sb = new StringBuilder(s.substring(0, start));
1469N/A sb.ensureCapacity(s.length() + 8);
1469N/A // since we can't copy sequences efficiently we copy char by char :(
1469N/A for (int i=start; i < s.length(); i++) {
1469N/A char c = s.charAt(i);
1469N/A switch (c) {
1469N/A case '&':
1469N/A sb.append("&amp;");
1469N/A break;
1469N/A case '>':
1469N/A sb.append("&gt;");
1469N/A break;
1469N/A case '<':
1469N/A sb.append("&lt;");
1469N/A break;
1469N/A case '\n':
1469N/A sb.append(eol);
1469N/A break;
1469N/A default:
1469N/A sb.append(c);
1469N/A }
0N/A }
1469N/A return sb.toString();
92N/A }
92N/A
1185N/A private static String versionP = htmlize(Info.getRevision());
1185N/A
92N/A /**
1185N/A * used by BUI - CSS needs this parameter for proper cache refresh (per
1185N/A * changeset) in client browser jel: but useless, since the page cached
1185N/A * anyway.
1190N/A *
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 /**
1185N/A * Convinience method for {@code breadcrumbPath(urlPrefix, path, '/')}.
1185N/A * @param urlPrefix prefix to add to each url
1185N/A * @param path path to crack
1185N/A * @return HTML markup fro the breadcrumb or the path itself.
1190N/A *
604N/A * @see #breadcrumbPath(String, String, char)
604N/A */
1185N/A public static String breadcrumbPath(String urlPrefix, String path) {
1185N/A return breadcrumbPath(urlPrefix, path, '/');
0N/A }
154N/A
583N/A private static final String anchorLinkStart = "<a href=\"";
583N/A private static final String anchorEnd = "</a>";
583N/A private static final String closeQuotedTag = "\">";
604N/A
604N/A /**
1205N/A * Convenience method for
1185N/A * {@code breadcrumbPath(urlPrefix, path, sep, "", false)}.
1190N/A *
1185N/A * @param urlPrefix prefix to add to each url
1185N/A * @param path path to crack
1185N/A * @param sep separator to use to crack the given path
1190N/A *
1185N/A * @return HTML markup fro the breadcrumb or the path itself.
1185N/A * @see #breadcrumbPath(String, String, char, String, boolean, boolean)
604N/A */
1185N/A public static String breadcrumbPath(String urlPrefix, String path, char sep)
1185N/A {
1185N/A return breadcrumbPath(urlPrefix, path, sep, "", false);
1185N/A }
1185N/A
1185N/A /**
1205N/A * Convenience method for
1185N/A * {@code breadcrumbPath(urlPrefix, path, sep, "", false, path.endsWith(sep)}.
1190N/A *
1185N/A * @param urlPrefix prefix to add to each url
1185N/A * @param path path to crack
1185N/A * @param sep separator to use to crack the given path
1185N/A * @param urlPostfix suffix to add to each url
1185N/A * @param compact if {@code true} the given path gets transformed into
1190N/A * its canonical form (.i.e. all '.' and '..' and double separators
1190N/A * removed, but not always resolves to an absolute path) before processing
1185N/A * starts.
1185N/A * @return HTML markup fro the breadcrumb or the path itself.
1185N/A * @see #breadcrumbPath(String, String, char, String, boolean, boolean)
1185N/A * @see #getCanonicalPath(String, char)
1185N/A */
1185N/A public static String breadcrumbPath(String urlPrefix, String path,
1185N/A char sep, String urlPostfix, boolean compact)
1185N/A {
1185N/A if (path == null || path.length() == 0) {
1185N/A return path;
1185N/A }
1185N/A return breadcrumbPath(urlPrefix, path, sep, urlPostfix, compact,
1185N/A path.charAt(path.length() - 1) == sep);
604N/A }
604N/A
604N/A /**
604N/A * Create a breadcrumb path to allow navigation to each element of a path.
1185N/A * Consecutive separators (<var>sep</var>) in the given <var>path</var> are
1185N/A * always collapsed into a single separator automatically. If
1185N/A * <var>compact</var> is {@code true} path gets translated into a canonical
1185N/A * path similar to {@link File#getCanonicalPath()}, however the current
1185N/A * working directory is assumed to be "/" and no checks are done (e.g.
1185N/A * neither whether the path [component] exists nor which type it is).
1190N/A *
1185N/A * @param urlPrefix
1185N/A * what should be prepend to the constructed URL
1185N/A * @param path
1185N/A * the full path from which the breadcrumb path is built.
1185N/A * @param sep
1185N/A * the character that separates the path components in
1185N/A * <var>path</var>
1185N/A * @param urlPostfix
1185N/A * what should be append to the constructed URL
1185N/A * @param compact
1185N/A * if {@code true}, a canonical path gets constructed before
1185N/A * processing.
1185N/A * @param isDir
1185N/A * if {@code true} a "/" gets append to the last path component's
1185N/A * link and <var>sep</var> to its name
1190N/A * @return <var>path</var> if it resolves to an empty or "/" or
1185N/A * {@code null} path, the HTML markup for the breadcrumb path otherwise.
604N/A */
1185N/A public static String breadcrumbPath(String urlPrefix, String path,
1185N/A char sep, String urlPostfix, boolean compact, boolean isDir)
1185N/A {
1185N/A if (path == null || path.length() == 0) {
1185N/A return path;
1185N/A }
1185N/A String[] pnames = normalize(path.split(escapeForRegex(sep)), compact);
1185N/A if (pnames.length == 0) {
1185N/A return path;
1185N/A }
1206N/A String prefix = urlPrefix == null ? "" : urlPrefix;
1206N/A String postfix = urlPostfix == null ? "" : urlPostfix;
1185N/A StringBuilder pwd = new StringBuilder(path.length() + pnames.length);
1185N/A StringBuilder markup =
1185N/A new StringBuilder( (pnames.length + 3 >> 1) * path.length()
1185N/A + pnames.length
1205N/A * (17 + prefix.length() + postfix.length()));
1185N/A int k = path.indexOf(pnames[0]);
1185N/A if (path.lastIndexOf(sep, k) != -1) {
1185N/A pwd.append('/');
1185N/A markup.append(sep);
604N/A }
1185N/A for (int i = 0; i < pnames.length; i++ ) {
1469N/A pwd.append(uriEncodePath(pnames[i]));
1185N/A if (isDir || i < pnames.length - 1) {
1185N/A pwd.append('/');
1185N/A }
1205N/A markup.append(anchorLinkStart).append(prefix).append(pwd)
1205N/A .append(postfix).append(closeQuotedTag).append(pnames[i])
1185N/A .append(anchorEnd);
1185N/A if (isDir || i < pnames.length - 1) {
1185N/A markup.append(sep);
1185N/A }
1185N/A }
1185N/A return markup.toString();
604N/A }
604N/A
604N/A /**
1190N/A * Normalize the given <var>path</var> to its canonical form. I.e. all
1190N/A * separators (<var>sep</var>) are replaced with a slash ('/'), all
1185N/A * double slashes are replaced by a single slash, all single dot path
1190N/A * components (".") of the formed path are removed and all double dot path
1190N/A * components (".." ) of the formed path are replaced with its parent or
1185N/A * '/' if there is no parent.
1185N/A * <p>
1185N/A * So the difference to {@link File#getCanonicalPath()} is, that this method
1185N/A * does not hit the disk (just string manipulation), resolves <var>path</var>
1190N/A * always against '/' and thus always returns an absolute path, which may
1190N/A * actually not exist, and which has a single trailing '/' if the given
1190N/A * <var>path</var> ends with the given <var>sep</var>.
1190N/A *
1190N/A * @param path path to mangle. If not absolute or {@code null}, the
1185N/A * current working directory is assumed to be '/'.
1185N/A * @param sep file separator to use to crack <var>path</var> into path
1185N/A * components
1185N/A * @return always a canonical path which starts with a '/'.
604N/A */
1185N/A public static String getCanonicalPath(String path, char sep) {
1185N/A if (path == null || path.length() == 0) {
1185N/A return "/";
1185N/A }
1185N/A String[] pnames = normalize(path.split(escapeForRegex(sep)), true);
1185N/A if (pnames.length == 0) {
1185N/A return "/";
604N/A }
1185N/A StringBuilder buf = new StringBuilder(path.length());
1185N/A buf.append('/');
1185N/A for (int i=0; i < pnames.length; i++) {
1185N/A buf.append(pnames[i]).append('/');
0N/A }
1185N/A if (path.charAt(path.length()-1) != sep) {
1190N/A // since is not a general purpose method. So we waive to handle
1185N/A // cases like:
1185N/A // || path.endsWith("/..") || path.endsWith("/.")
1185N/A buf.setLength(buf.length()-1);
1185N/A }
1185N/A return buf.toString();
604N/A }
604N/A
1297N/A private final static Pattern EMAIL_PATTERN =
1297N/A Pattern.compile("([^<\\s]+@[^>\\s]+)");
1297N/A
1297N/A /**
1297N/A * Get email address of the author.
1297N/A *
1297N/A * @param author
1297N/A * string containing author and possibly email address.
1384N/A * @return the first email address found in the given parameter, the
1384N/A * parameter otherwise. Returned values are whitespace trimmed.
1297N/A */
1297N/A public static String getEmail(String author) {
1297N/A Matcher email_matcher = EMAIL_PATTERN.matcher(author);
1297N/A String email = author;
1297N/A if (email_matcher.find()) {
1297N/A email = email_matcher.group(1).trim();
1297N/A }
1297N/A
1384N/A return email.trim();
1297N/A }
1297N/A
604N/A /**
1185N/A * Remove all empty and {@code null} string elements from the given
1185N/A * <var>names</var> and optionally all redundant information like "." and
1185N/A * "..".
1190N/A *
1185N/A * @param names
1185N/A * names to check
1185N/A * @param canonical
1185N/A * if {@code true}, remove redundant elements as well.
1185N/A * @return a possible empty array of names all with a length &gt; 0.
604N/A */
1185N/A private static String[] normalize(String[] names, boolean canonical) {
1185N/A LinkedList<String> res = new LinkedList<String>();
1185N/A if (names == null || names.length == 0) {
1185N/A return new String[0];
1185N/A }
1185N/A for (int i = 0; i < names.length; i++ ) {
1185N/A if (names[i] == null || names[i].length() == 0) {
1185N/A continue;
1185N/A }
1185N/A if (canonical) {
1185N/A if (names[i].equals("..")) {
1233N/A if (!res.isEmpty()) {
1185N/A res.removeLast();
605N/A }
1185N/A } else if (names[i].equals(".")) {
1185N/A continue;
1185N/A } else {
1185N/A res.add(names[i]);
605N/A }
605N/A } else {
1185N/A res.add(names[i]);
605N/A }
605N/A }
1185N/A return res.size() == names.length ? names : res.toArray(new String[res
1185N/A .size()]);
604N/A }
604N/A
604N/A /**
1185N/A * Generate a regex that matches the specified character. Escape it in case
1185N/A * it is a character that has a special meaning in a regex.
1190N/A *
1185N/A * @param c
1185N/A * 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");
1185N/A String hex = Integer.toHexString(c);
1185N/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
1205N/A private static NumberFormat FORMATTER = new DecimalFormat("#,###,###,###.#");
1185N/A
1185N/A /**
1185N/A * Convert the given size into a human readable string.
1185N/A * @param num size to convert.
1185N/A * @return a readable string
1185N/A */
1185N/A public static String readableSize(long num) {
1185N/A float l = num;
1185N/A NumberFormat formatter = (NumberFormat) FORMATTER.clone();
154N/A if (l < 1024) {
1185N/A return formatter.format(l) + ' '; // for none-dirs append 'B'? ...
154N/A } else if (l < 1048576) {
1185N/A return (formatter.format(l / 1024) + " KiB");
0N/A } else {
1185N/A return ("<b>" + formatter.format(l / 1048576) + " MiB</b>");
0N/A }
0N/A }
154N/A
878N/A /**
1185N/A * Generate a string from the given path and date in a way that allows
1190N/A * stable lexicographic sorting (i.e. gives always the same results) as a
1190N/A * walk of the file hierarchy. Thus null character (\u0000) is used both
1185N/A * to separate directory components and to separate the path from the date.
1185N/A * @param path path to mangle.
1185N/A * @param date date string to use.
1185N/A * @return the mangled path.
0N/A */
1185N/A public static String path2uid(String path, String date) {
153N/A return path.replace('/', '\u0000') + "\u0000" + date;
0N/A }
154N/A
1185N/A /**
1190N/A * The reverse operation for {@link #path2uid(String, String)} - re-creates
1185N/A * the unmangled path from the given uid.
1185N/A * @param uid uid to unmangle.
1185N/A * @return the original path.
1185N/A */
0N/A public static String uid2url(String uid) {
1185N/A String url = uid.replace('\u0000', '/');
0N/A return url.substring(0, url.lastIndexOf('/')); // remove date from end
0N/A }
154N/A
849N/A /**
1469N/A * Append '&amp;name=value" properly URI encoded to the given buffer. If
1469N/A * the given <var>value</var> is {@code null}, this method does nothing.
1190N/A *
1185N/A * @param buf where to append the query string
1185N/A * @param key the name of the parameter to add. Append as is!
1469N/A * @param value the decoded value for the given parameter.
1185N/A * @throws NullPointerException if the given buffer is {@code null}.
1469N/A * @see #uriEncodeQueryValue(String)
1185N/A */
1205N/A public static void appendQuery(StringBuilder buf, String key,
1190N/A String value)
1185N/A {
1185N/A if (value != null) {
1469N/A buf.append("&amp;").append(key).append('=')
1469N/A .append(uriEncodeQueryValue(value));
1185N/A }
1185N/A }
1190N/A
1469N/A /* ASCII chars 0..63, which need to be escaped in an URI path. The segment
1469N/A * delimiter '/' (47) is not added to this set, because it is used to
1469N/A * encode file pathes which use the same delimiter for path components.
1469N/A * Since we do not use any path parameters we add ';' (59) as well.
1469N/A * Furthermore to avoid the need of a 2nd run to escape '&' for HTML
1469N/A * attributes, we encode '&' (38) as well.
1469N/A * RFC 3986: chars 0..32 34 35 37 60 62 63 */
1469N/A private static final long PATH_ESCAPE_NC_L = 1L<<33-1 | 1L<<'"' | 1L<<'#'
1469N/A | 1L<<'%' | 1L<<'<' | 1L<<'>' | 1L<<'?' | 1L<<';' | 1L<<'&';
1469N/A /* RFC 3986: path-noscheme - ':' (58) in the first segment needs to be encoded */
1469N/A private static final long PATH_ESCAPE_L = PATH_ESCAPE_NC_L | 1L<<':';
1469N/A /* ASCII chars 64..127, which need to be escaped in an URI path.
1366N/A * NOTE: URIs allow '~' unescaped, URLs not.
1469N/A * RFC 3986: chars 91 92 93 94 96 123 124 125 127 */
1384N/A private static final long PATH_ESCAPE_H = 1L<<'['-64 | 1L<<'\\'-64 | 1L<<']'-64
1366N/A | 1L<<'^'-64 | 1L<<'`'-64 | 1L<<'{'-64 | 1L<<'|'-64 | 1L<<'}'-64 | 1L<<63;
1469N/A /* ASCII chars 0..63, which need to be escaped in an URI query string as
1469N/A * well as fragment part.
1469N/A * RFC 3986: chars 32 34 35 37 60 62 */
1469N/A private static final long QUERY_ESCAPE_L =
1469N/A PATH_ESCAPE_NC_L ^ (1L<<'?' | 1L<<';' | 1L<<'&');
1469N/A /* ASCII chars 64..127, which need to be escaped in an URI query string as
1469N/A * well as fragment part.
1469N/A * RFC 3986: chars 91 92 93 94 96 123 124 125 127 */
1469N/A private static final long QUERY_ESCAPE_H = PATH_ESCAPE_H;
1469N/A
1469N/A /* Query string starts at the left-most '?', ends at the next '#' or if not
1469N/A * available at the end of the URI string and contains name/value pairs
1469N/A * of the form $name['='[$value]] delimited by '&'. So no need to encode '='
1469N/A * for query values, but for query names. */
1469N/A private static final long QUERY_VALUE_ESCAPE_L = QUERY_ESCAPE_L | 1L<<'&';
1469N/A private static final long QUERY_VALUE_ESCAPE_H = QUERY_ESCAPE_H;
1469N/A private static final long QUERY_NAME_ESCAPE_L = QUERY_VALUE_ESCAPE_L | 1L<<'=';
1469N/A private static final long QUERY_NAME_ESCAPE_H = QUERY_ESCAPE_H;
1469N/A
1469N/A /**
1469N/A * URI encode the given path using UTF-8 encoding for non-ASCII characters.
1469N/A * In addition ';', '&' (and ':' for relative paths) get encoded as well,
1469N/A * but not '/'. This method should not be used to encode query strings - for
1469N/A * this {@link #uriEncodeQueryName(String)} and
1469N/A * {@link #uriEncodeQueryValue(String)} should be used.
1469N/A * NOTE: Since single quote (') gets not encoded, the encoded result should
1469N/A * be enclosed in double quotes (") when used as attribute values!
1469N/A *
1469N/A * @param path path to encode.
1469N/A * @return the encoded path.
1469N/A * @throws NullPointerException if a parameter is {@code null}
1469N/A * @see "http://tools.ietf.org/html/rfc3986"
1469N/A */
1469N/A public static String uriEncodePath(String path) {
1469N/A if (path.length() == 0) {
1469N/A return path;
1469N/A }
1469N/A return uriEncode(path,
1469N/A path.charAt(0) == '/' ? PATH_ESCAPE_NC_L : PATH_ESCAPE_L,
1469N/A PATH_ESCAPE_H);
1469N/A }
1469N/A
1469N/A /**
1469N/A * URI encode the given value for a URI query string pair using UTF-8
1469N/A * encoding for non-ASCII characters ('&' gets encoded as well). It can also
1469N/A * be used to encode URI fragments properly.
1469N/A * NOTE: Since single quote (') gets not encoded, the encoded result should
1469N/A * be enclosed in double quotes (") when used as attribute values!
1469N/A * @param value value to encode.
1469N/A * @return the encoded value.
1469N/A * @throws NullPointerException if a parameter is {@code null}
1469N/A * @see "http://tools.ietf.org/html/rfc3986"
1469N/A * @see #uriEncodeQueryName(String)
1469N/A */
1469N/A public static String uriEncodeQueryValue(String value) {
1469N/A return uriEncode(value, QUERY_VALUE_ESCAPE_L, QUERY_VALUE_ESCAPE_H);
1469N/A }
1469N/A
1469N/A /**
1469N/A * URI encode the given name for a URI query string pair using UTF-8
1469N/A * encoding for non-ASCII characters ('=' and '&' get encoded as well).
1469N/A * NOTE: Since single quote (') gets not encoded, the encoded result should
1469N/A * be enclosed in double quotes (") when used as attribute values!
1469N/A * @param name name to encode.
1469N/A * @return the encoded name.
1469N/A * @throws NullPointerException if a parameter is {@code null}
1469N/A * @see "http://tools.ietf.org/html/rfc3986"
1469N/A * @see #uriEncodeQueryValue(String)
1469N/A */
1469N/A public static String uriEncodeQueryName(String name) {
1469N/A return uriEncode(name, QUERY_NAME_ESCAPE_L, QUERY_NAME_ESCAPE_H);
1469N/A }
1469N/A
1469N/A /* int -> hex encoding helper */
1366N/A private final static char[] hex = "0123456789ABCDEF".toCharArray();
1366N/A
1366N/A private static void appendEscape(StringBuilder sb, byte b) {
1366N/A sb.append('%').append(hex[b >> 4 & 0x0F]).append(hex[b & 0x0F]);
1366N/A }
1366N/A
1469N/A private static String uriEncode(String s, long lowMask, long hiMask) {
1366N/A int i=0;
1469N/A for (;i < s.length(); i++) {
1469N/A char c = s.charAt(i);
1366N/A if (c < 64) {
1469N/A if ((lowMask & (1L << c)) != 0) {
1366N/A break;
1366N/A }
1366N/A } else if (c < 0x80) {
1469N/A if ((hiMask & (1L << c-64)) != 0) {
1366N/A break;
1366N/A }
1376N/A } else {
1366N/A break;
1366N/A }
1366N/A }
1469N/A if (i == s.length()) {
1469N/A return s;
158N/A }
1469N/A StringBuilder sb = new StringBuilder(s.length() - i + 24);
1469N/A sb.append(s, 0, i);
1469N/A for(; i < s.length(); i++) {
1469N/A char c = s.charAt(i);
1366N/A if (c < 64) {
1469N/A if ((lowMask & (1L << c)) != 0) {
1366N/A appendEscape(sb, (byte) c);
1366N/A } else {
1366N/A sb.append(c);
1376N/A }
1366N/A } else if (c < 0x80) {
1469N/A if ((hiMask & (1L << c-64)) != 0) {
1366N/A appendEscape(sb, (byte) c);
1366N/A } else {
1366N/A sb.append(c);
1366N/A }
1366N/A } else {
1366N/A ByteBuffer bb = Charset.forName("UTF-8")
1469N/A .encode(CharBuffer.wrap(s, i, s.length()));
1366N/A while (bb.hasRemaining()) {
1366N/A int b = bb.get() & 0xff;
1366N/A if (b < 0x80) {
1366N/A sb.append((char) b);
1366N/A } else {
1366N/A appendEscape(sb, (byte) b);
1366N/A }
1366N/A }
1366N/A break;
1376N/A }
158N/A }
1376N/A return sb.toString();
158N/A }
158N/A
1185N/A /**
1469N/A * Replace all quote and ampersand characters (ASCII 0x22, 0x26) with the
1469N/A * corresponding html entity (&amp;quot; , &amp;amp;).
1185N/A * @param q string to escape.
1190N/A * @return an empty string if a parameter is {@code null}, the mangled
1185N/A * string otherwise.
1185N/A */
0N/A public static String formQuoteEscape(String q) {
1185N/A if (q == null || q.isEmpty()) {
58N/A return "";
58N/A }
0N/A char c;
1469N/A int pos = -1;
1185N/A for (int i = 0; i < q.length(); i++ ) {
0N/A c = q.charAt(i);
1469N/A if (c == '"' || c == '&') {
1469N/A pos = i;
1469N/A break;
1469N/A }
1469N/A }
1469N/A if (pos == -1) {
1469N/A return q;
1469N/A }
1469N/A StringBuilder sb = new StringBuilder(q.substring(0, pos));
1469N/A for (int i = pos; i < q.length(); i++ ) {
1469N/A c = q.charAt(i);
154N/A if (c == '"') {
0N/A sb.append("&quot;");
1469N/A } else if (c == '&') {
1469N/A sb.append("&amp;");
0N/A } else {
0N/A sb.append(c);
0N/A }
0N/A }
0N/A return sb.toString();
0N/A }
1025N/A
1388N/A /** span open tag incl. css class used to tag removed source code */
1388N/A public static final String SPAN_D = "<span class=\"m\">";
1388N/A /** span open tag incl. css class used to tag added source code */
1388N/A public static final String SPAN_A = "<span class=\"p\">";
1185N/A private static final String SPAN_E = "</span>";
1185N/A private static final int SPAN_LEN = SPAN_D.length() + SPAN_E.length();
1190N/A
1025N/A /**
1185N/A * Tag changes in the given <var>line1</var> and <var>line2</var>
1185N/A * for highlighting. Removed parts are tagged with CSS class {@code d},
1185N/A * new parts are tagged with CSS class {@code a} using a {@code span}
1185N/A * element.
1190N/A *
1185N/A * @param line1 line of the original file
1185N/A * @param line2 line of the changed/new file
1185N/A * @return the tagged lines (field[0] ~= line1, field[1] ~= line2).
1190N/A * @throws NullPointerException if one of the given parameters is {@code null}.
1025N/A */
1469N/A public static String[] diffline(String line1, String line2) {
1185N/A int m = line1.length();
1185N/A int n = line2.length();
1185N/A if (n == 0 || m == 0) {
1248N/A return new String[] {line1.toString(), line2.toString()};
1185N/A }
1025N/A
1185N/A int s = 0;
1185N/A char[] csl1 = new char[m + SPAN_LEN];
1185N/A line1.getChars(0, m--, csl1, 0);
1185N/A char[] csl2 = new char[n + SPAN_LEN];
1185N/A line2.getChars(0, n--, csl2, 0);
1185N/A while (s <= m && s <= n && csl1[s] == csl2[s]) {
1185N/A s++ ;
1185N/A }
1185N/A while (s <= m && s <= n && csl1[m] == csl2[n]) {
1185N/A m-- ;
1185N/A n-- ;
1185N/A }
1025N/A
1185N/A String[] ret = new String[2];
1185N/A // deleted
1185N/A if (s <= m) {
1185N/A m++;
1185N/A System.arraycopy(csl1, m, csl1, m + SPAN_LEN, line1.length() - m);
1185N/A System.arraycopy(csl1, s, csl1, s + SPAN_D.length(), m - s);
1185N/A SPAN_E.getChars(0, SPAN_E.length(), csl1, m + SPAN_D.length());
1185N/A SPAN_D.getChars(0, SPAN_D.length(), csl1, s);
1185N/A ret[0] = new String(csl1);
1185N/A } else {
1185N/A ret[0] = line1.toString();
1185N/A }
1185N/A // added
1185N/A if (s <= n) {
1185N/A n++;
1185N/A System.arraycopy(csl2, n, csl2, n + SPAN_LEN, line2.length() - n);
1185N/A System.arraycopy(csl2, s, csl2, s + SPAN_A.length(), n - s);
1185N/A SPAN_E.getChars(0, SPAN_E.length(), csl2, n + SPAN_A.length());
1185N/A SPAN_A.getChars(0, SPAN_A.length(), csl2, s);
1185N/A ret[1] = new String(csl2);
1185N/A } else {
1185N/A ret[1] = line2.toString();
1185N/A }
1185N/A return ret;
1025N/A }
1124N/A
1124N/A /**
1124N/A * Dump the configuration as an HTML table.
1190N/A *
1185N/A * @param out
1185N/A * destination for the HTML output
1185N/A * @throws IOException
1185N/A * if an error happens while writing to {@code out}
1185N/A * @throws HistoryException
1185N/A * if the history guru cannot be accesses
1124N/A */
1185N/A @SuppressWarnings("boxing")
1185N/A public static void dumpConfiguration(Appendable out) throws IOException,
1185N/A HistoryException
1185N/A {
1124N/A out.append("<table border=\"1\" width=\"100%\">");
1124N/A out.append("<tr><th>Variable</th><th>Value</th></tr>");
1124N/A RuntimeEnvironment env = RuntimeEnvironment.getInstance();
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());
1262N/A printTableRow(out, "User page suffix", env.getUserPageSuffix());
1124N/A printTableRow(out, "Review page", env.getReviewPage());
1124N/A printTableRow(out, "Review pattern", env.getReviewPattern());
1124N/A printTableRow(out, "Using projects", env.hasProjects());
1356N/A printTableRow(out, "Styles", env.getWebappLAF());
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 printTableRow(out, "Index word limit", env.getIndexWordLimit());
1124N/A printTableRow(out, "Allow leading wildcard in search",
1185N/A env.isAllowLeadingWildcard());
1185N/A printTableRow(out, "History cache", HistoryGuru.getInstance()
1185N/A .getCacheInfo());
1330N/A printTableRow(out, "Version", Info.getVersion() + '@' + Info.getRevision()
1330N/A + " " + new Date(Info.getLastModified()));
1330N/A printTableRow(out, "Last Modified",
1330N/A new Date(env.getConfiguration().getLastModified()));
1358N/A out.append("<tr><td>YUI modules</td><td id='yui_modules'></td></tr>");
1124N/A out.append("</table>");
1124N/A }
1124N/A
1124N/A /**
1384N/A * Just read the given source and dump as is to the given destination.
1190N/A * Does nothing, if one or more of the parameters is {@code null}.
1185N/A * @param out write destination
1185N/A * @param in source to read
1185N/A * @throws IOException as defined by the given reader/writer
1185N/A * @throws NullPointerException if a parameter is {@code null}.
1185N/A */
1205N/A public static void dump(Writer out, Reader in) throws IOException {
1185N/A if (in == null || out == null) {
1185N/A return;
1185N/A }
1185N/A char[] buf = new char[8192];
1185N/A int len = 0;
1185N/A while ((len = in.read(buf)) >= 0) {
1185N/A out.write(buf, 0, len);
1185N/A }
1185N/A }
1190N/A
1185N/A /**
1384N/A * Silently dump a file to the given destination. All {@link IOException}s
1185N/A * gets caught and logged, but not re-thrown.
1190N/A *
1185N/A * @param out dump destination
1185N/A * @param dir directory, which should contains the file.
1185N/A * @param filename the basename of the file to dump.
1185N/A * @param compressed if {@code true} the denoted file is assumed to be
1190N/A * gzipped.
1185N/A * @return {@code true} on success (everything read and written).
1185N/A * @throws NullPointerException if a parameter is {@code null}.
1185N/A */
1190N/A public static boolean dump(Writer out, File dir, String filename,
1190N/A boolean compressed)
1185N/A {
1185N/A return dump(out, new File(dir, filename), compressed);
1185N/A }
1185N/A
1185N/A /**
1384N/A * Silently dump a file to the given destination. All {@link IOException}s
1185N/A * gets caught and logged, but not re-thrown.
1190N/A *
1185N/A * @param out dump destination
1185N/A * @param file file to dump.
1185N/A * @param compressed if {@code true} the denoted file is assumed to be
1190N/A * gzipped.
1185N/A * @return {@code true} on success (everything read and written).
1185N/A * @throws NullPointerException if a parameter is {@code null}.
1185N/A */
1463N/A @SuppressWarnings("resource")
1185N/A public static boolean dump(Writer out, File file, boolean compressed) {
1185N/A if (!file.exists()) {
1185N/A return false;
1185N/A }
1185N/A FileInputStream fis = null;
1185N/A GZIPInputStream gis = null;
1185N/A Reader in = null;
1185N/A try {
1185N/A if (compressed) {
1185N/A fis = new FileInputStream(file);
1384N/A gis = new GZIPInputStream(fis, 4096);
1185N/A in = new InputStreamReader(gis);
1185N/A } else {
1185N/A in = new FileReader(file);
1185N/A }
1185N/A dump(out, in);
1185N/A return true;
1185N/A } catch(IOException e) {
1327N/A logger.warning("An error occured while piping file '" + file + "': "
1327N/A + e.getMessage());
1327N/A logger.log(Level.FINE, "dump", e);
1185N/A } finally {
1195N/A IOUtils.close(in);
1195N/A IOUtils.close(gis);
1195N/A IOUtils.close(fis);
1185N/A }
1185N/A return false;
1185N/A }
1185N/A
1185N/A /**
1124N/A * Print a row in an HTML table.
1190N/A *
1185N/A * @param out
1185N/A * destination for the HTML output
1185N/A * @param cells
1185N/A * the values to print in the cells of the row
1185N/A * @throws IOException
1185N/A * if an error happens while writing to {@code out}
1124N/A */
1124N/A private static void printTableRow(Appendable out, Object... cells)
1185N/A throws IOException
1185N/A {
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();
1469N/A out.append(htmlize(str, "<br/>"));
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).
1190N/A *
1185N/A * @param out
1185N/A * destination for the HTML output
1185N/A * @param items
1185N/A * the list items
1185N/A * @throws IOException
1185N/A * if an error happens while writing to {@code out}
1124N/A */
1185N/A private static void printUnorderedList(Appendable out,
1185N/A Collection<String> items) throws IOException
1185N/A {
1124N/A out.append("<ul>");
1124N/A for (String item : items) {
1124N/A out.append("<li>");
1469N/A out.append(htmlize(item));
1124N/A out.append("</li>");
1124N/A }
1124N/A out.append("</ul>");
1124N/A }
1145N/A
1145N/A /**
1145N/A * Create a string literal for use in JavaScript functions.
1190N/A *
1384N/A * @param str the string to be represented by the literal
1384N/A * @return a JavaScript string literal. {@code null} values are converted to ''.
1145N/A */
1145N/A public static String jsStringLiteral(String str) {
1384N/A if (str == null) {
1384N/A return "\"\"";
1384N/A }
1145N/A StringBuilder sb = new StringBuilder();
1145N/A sb.append('"');
1185N/A for (int i = 0; i < str.length(); i++ ) {
1145N/A char c = str.charAt(i);
1145N/A switch (c) {
1145N/A case '"':
1145N/A sb.append("\\\"");
1145N/A break;
1145N/A case '\\':
1145N/A sb.append("\\\\");
1145N/A break;
1145N/A case '\n':
1145N/A sb.append("\\n");
1145N/A break;
1145N/A case '\r':
1145N/A sb.append("\\r");
1145N/A break;
1145N/A default:
1145N/A sb.append(c);
1145N/A }
1145N/A }
1145N/A sb.append('"');
1145N/A return sb.toString();
1145N/A }
1384N/A
1384N/A /**
1384N/A * Convert the given array into JSON format and write it to the given stream.
1384N/A * If <var>name</var> is given, the result is {@code name: [ value, ...]},
1384N/A * otherwise {@code [ value, ... ]}.
1384N/A * @param out where to write the json formatted array
1384N/A * @param name name of the array. Might be {@code null}.
1384N/A * @param values array to convert. {@code null} are converted to ''.
1384N/A * @throws IOException
1384N/A * @see #jsStringLiteral(String)
1384N/A * @see #writeJsonArray(Writer, String, String[])
1384N/A */
1384N/A public static void writeJsonArray(Writer out, String name,
1384N/A Collection<String> values) throws IOException
1384N/A {
1384N/A // could just call writeJsonArray(out, name, values.toArray(new String[]))
1384N/A // but this can be an issue for big collections - so we rather
1384N/A // "duplicate" the code
1384N/A if (name != null) {
1384N/A out.write('"');
1384N/A out.write(name);
1384N/A out.write("\":");
1384N/A }
1384N/A out.write('[');
1384N/A Iterator<String> i = values.iterator();
1384N/A if (i.hasNext()) {
1384N/A out.write(jsStringLiteral(i.next()));
1384N/A while (i.hasNext()) {
1384N/A out.write(',');
1384N/A out.write(jsStringLiteral(i.next()));
1384N/A }
1384N/A }
1384N/A out.write(']');
1384N/A }
1384N/A
1384N/A /**
1384N/A * Convert the given array into JSON format and write it to the given stream.
1384N/A * If <var>name</var> is given, the result is {@code name: [ value, ...]},
1384N/A * otherwise {@code [ value, ... ]}.
1384N/A * @param out where to write the json formatted array
1384N/A * @param name name of the array. Might be {@code null}.
1384N/A * @param values array to convert. {@code null} are converted to ''.
1384N/A * @throws IOException
1384N/A * @see #jsStringLiteral(String)
1384N/A * @see #writeJsonArray(Writer, String, Collection)
1384N/A */
1384N/A public static void writeJsonArray(Writer out, String name, String[] values) throws IOException {
1384N/A if (name != null) {
1384N/A out.write('"');
1384N/A out.write(name);
1384N/A out.write("\":");
1384N/A }
1384N/A out.write('[');
1384N/A if (values.length > 0) {
1384N/A out.write(jsStringLiteral(values[0]));
1384N/A for (int i=1; i < values.length; i++) {
1384N/A out.write(',');
1384N/A out.write(jsStringLiteral(values[i]));
1384N/A }
1384N/A }
1384N/A out.write(']');
1384N/A }
0N/A}