Util.java revision 1297
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend/*
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * CDDL HEADER START
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * The contents of this file are subject to the terms of the
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Common Development and Distribution License (the "License").
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * You may not use this file except in compliance with the License.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
8b7e19de6d547ab1ad4256316fbf0d2497f724f8humbedooh * See LICENSE.txt included in this distribution for the specific
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * language governing permissions and limitations under the License.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * When distributing Covered Code, include this CDDL HEADER in each
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * file and include the License file at LICENSE.txt.
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * If applicable, add the following below this CDDL HEADER, with the
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * fields enclosed by brackets "[]" replaced with your own identifying
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * information: Portions Copyright [yyyy] [name of copyright owner]
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
af33a4994ae2ff15bc67d19ff1a7feb906745bf8rbowen * CDDL HEADER END
3f08db06526d6901aa08c110b5bc7dde6bc39905nd */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend/*
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
8b7e19de6d547ab1ad4256316fbf0d2497f724f8humbedooh * Portions Copyright 2011 Jens Elkner.
3b3b7fc78d1f5bfc2769903375050048ff41ff26nd */
f086b4b402fa9a2fefc7dda85de2a3cc1cd0a654rjungpackage org.opensolaris.opengrok.web;
3b3b7fc78d1f5bfc2769903375050048ff41ff26nd
3b3b7fc78d1f5bfc2769903375050048ff41ff26ndimport java.io.File;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.io.FileInputStream;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.io.FileReader;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.io.IOException;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.io.InputStreamReader;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.io.Reader;
8b7e19de6d547ab1ad4256316fbf0d2497f724f8humbedoohimport java.io.UnsupportedEncodingException;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.io.Writer;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.net.URI;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.net.URISyntaxException;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.net.URLEncoder;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.text.DecimalFormat;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.text.NumberFormat;
8b7e19de6d547ab1ad4256316fbf0d2497f724f8humbedoohimport java.util.Collection;
8b7e19de6d547ab1ad4256316fbf0d2497f724f8humbedoohimport java.util.LinkedList;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.util.logging.Level;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.util.logging.Logger;
dc06c0ac17fcd3d721bb865d7fc5d2cefe67c57ftrawickimport java.util.regex.Matcher;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.util.regex.Pattern;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport java.util.zip.GZIPInputStream;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport org.opensolaris.opengrok.Info;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport org.opensolaris.opengrok.OpenGrokLogger;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport org.opensolaris.opengrok.history.Annotation;
8b7e19de6d547ab1ad4256316fbf0d2497f724f8humbedoohimport org.opensolaris.opengrok.history.HistoryException;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport org.opensolaris.opengrok.history.HistoryGuru;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendimport org.opensolaris.opengrok.util.IOUtils;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend/**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Class for useful functions.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aendpublic final class Util {
30471a4650391f57975f60bbb6e4a90be7b284bfhumbedooh private Util() {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend // singleton
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Return a string which represents a <code>CharSequence</code> in HTML.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param q
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * a character sequence
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @return a string representing the character sequence in HTML
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend public static String htmlize(CharSequence q) {
8b7e19de6d547ab1ad4256316fbf0d2497f724f8humbedooh StringBuilder sb = new StringBuilder(q.length() * 2);
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend htmlize(q, sb);
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend return sb.toString();
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Append a character sequence to the given destination whereby
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * special characters for HTML are escaped accordingly.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param q a character sequence to esacpe
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param dest where to append the character sequence to
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend public static void htmlize(CharSequence q, StringBuilder dest) {
d8925e7b1eb8644294705d7132173f6fcfe0350ahumbedooh for (int i = 0; i < q.length(); i++ ) {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend htmlize(q.charAt(i), dest);
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Append a character array to the given destination whereby
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * special characters for HTML are escaped accordingly.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param cs characters to esacpe
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param length max. number of characters to append, starting from index 0.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param dest where to append the character sequence to
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend public static void htmlize(char[] cs, int length, StringBuilder dest) {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend int len = length;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend if (cs.length < length) {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend len = cs.length;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend for (int i = 0; i < len; i++ ) {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend htmlize(cs[i], dest);
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Append a character to the given destination whereby special characters
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * special for HTML are escaped accordingly.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param c the character to append
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param dest where to append the character to
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend private static void htmlize(char c, StringBuilder dest) {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend switch (c) {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend case '&':
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend dest.append("&amp;");
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend break;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend case '>':
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend dest.append("&gt;");
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend break;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend case '<':
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend dest.append("&lt;");
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend break;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend case '\n':
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend dest.append("<br/>");
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend break;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend default:
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend dest.append(c);
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend private static String versionP = htmlize(Info.getRevision());
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * used by BUI - CSS needs this parameter for proper cache refresh (per
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * changeset) in client browser jel: but useless, since the page cached
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * anyway.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @return html escaped version (hg changeset)
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend public static String versionParameter() {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend return versionP;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Convinience method for {@code breadcrumbPath(urlPrefix, path, '/')}.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param urlPrefix prefix to add to each url
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param path path to crack
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @return HTML markup fro the breadcrumb or the path itself.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @see #breadcrumbPath(String, String, char)
1050464f9f91f75e7a1c5c3daf3fb7b8aa74592ahumbedooh */
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh public static String breadcrumbPath(String urlPrefix, String path) {
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh return breadcrumbPath(urlPrefix, path, '/');
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh private static final String anchorLinkStart = "<a href=\"";
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh private static final String anchorClassStart = "<a class=\"";
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh private static final String anchorEnd = "</a>";
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend private static final String closeQuotedTag = "\">";
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh /**
15e38d431fea66258ef33960b39edee496c3c9c2humbedooh * Convenience method for
1050464f9f91f75e7a1c5c3daf3fb7b8aa74592ahumbedooh * {@code breadcrumbPath(urlPrefix, path, sep, "", false)}.
1050464f9f91f75e7a1c5c3daf3fb7b8aa74592ahumbedooh *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param urlPrefix prefix to add to each url
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param path path to crack
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param sep separator to use to crack the given path
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @return HTML markup fro the breadcrumb or the path itself.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @see #breadcrumbPath(String, String, char, String, boolean, boolean)
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend public static String breadcrumbPath(String urlPrefix, String path, char sep)
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend return breadcrumbPath(urlPrefix, path, sep, "", false);
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Convenience method for
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * {@code breadcrumbPath(urlPrefix, path, sep, "", false, path.endsWith(sep)}.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param urlPrefix prefix to add to each url
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param path path to crack
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param sep separator to use to crack the given path
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param urlPostfix suffix to add to each url
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param compact if {@code true} the given path gets transformed into
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * its canonical form (.i.e. all '.' and '..' and double separators
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * removed, but not always resolves to an absolute path) before processing
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * starts.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @return HTML markup fro the breadcrumb or the path itself.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @see #breadcrumbPath(String, String, char, String, boolean, boolean)
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @see #getCanonicalPath(String, char)
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend */
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend public static String breadcrumbPath(String urlPrefix, String path,
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend char sep, String urlPostfix, boolean compact)
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend if (path == null || path.length() == 0) {
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend return path;
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend return breadcrumbPath(urlPrefix, path, sep, urlPostfix, compact,
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend path.charAt(path.length() - 1) == sep);
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend }
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend /**
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Create a breadcrumb path to allow navigation to each element of a path.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * Consecutive separators (<var>sep</var>) in the given <var>path</var> are
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * always collapsed into a single separator automatically. If
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * <var>compact</var> is {@code true} path gets translated into a canonical
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * path similar to {@link File#getCanonicalPath()}, however the current
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * working directory is assumed to be "/" and no checks are done (e.g.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * neither whether the path [component] exists nor which type it is).
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend *
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param urlPrefix
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * what should be prepend to the constructed URL
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param path
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * the full path from which the breadcrumb path is built.
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * @param sep
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * the character that separates the path components in
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend * <var>path</var>
3b3b7fc78d1f5bfc2769903375050048ff41ff26nd * @param urlPostfix
f086b4b402fa9a2fefc7dda85de2a3cc1cd0a654rjung * what should be append to the constructed URL
727872d18412fc021f03969b8641810d8896820bhumbedooh * @param compact
0d0ba3a410038e179b695446bb149cce6264e0abnd * if {@code true}, a canonical path gets constructed before
727872d18412fc021f03969b8641810d8896820bhumbedooh * processing.
cc7e1025de9ac63bd4db6fe7f71c158b2cf09fe4humbedooh * @param isDir
0d0ba3a410038e179b695446bb149cce6264e0abnd * if {@code true} a "/" gets append to the last path component's
cc7e1025de9ac63bd4db6fe7f71c158b2cf09fe4humbedooh * link and <var>sep</var> to its name
727872d18412fc021f03969b8641810d8896820bhumbedooh * @return <var>path</var> if it resolves to an empty or "/" or
0d0ba3a410038e179b695446bb149cce6264e0abnd * {@code null} path, the HTML markup for the breadcrumb path otherwise.
0d0ba3a410038e179b695446bb149cce6264e0abnd */
0d0ba3a410038e179b695446bb149cce6264e0abnd public static String breadcrumbPath(String urlPrefix, String path,
ac082aefa89416cbdc9a1836eaf3bed9698201c8humbedooh char sep, String urlPostfix, boolean compact, boolean isDir)
0d0ba3a410038e179b695446bb149cce6264e0abnd {
0d0ba3a410038e179b695446bb149cce6264e0abnd if (path == null || path.length() == 0) {
0d0ba3a410038e179b695446bb149cce6264e0abnd return path;
727872d18412fc021f03969b8641810d8896820bhumbedooh }
0d0ba3a410038e179b695446bb149cce6264e0abnd String[] pnames = normalize(path.split(escapeForRegex(sep)), compact);
0d0ba3a410038e179b695446bb149cce6264e0abnd if (pnames.length == 0) {
30471a4650391f57975f60bbb6e4a90be7b284bfhumbedooh return path;
07dc96d063d49299da433f84b5c5681da9bbdf68rbowen }
af33a4994ae2ff15bc67d19ff1a7feb906745bf8rbowen String prefix = urlPrefix == null ? "" : urlPrefix;
0d0ba3a410038e179b695446bb149cce6264e0abnd String postfix = urlPostfix == null ? "" : urlPostfix;
7fec19672a491661b2fe4b29f685bc7f4efa64d4nd StringBuilder pwd = new StringBuilder(path.length() + pnames.length);
7fec19672a491661b2fe4b29f685bc7f4efa64d4nd StringBuilder markup =
7fec19672a491661b2fe4b29f685bc7f4efa64d4nd new StringBuilder( (pnames.length + 3 >> 1) * path.length()
38dd0bd0209a7e29ea44e66bfc3bec8edc03e9aend + pnames.length
* (17 + prefix.length() + postfix.length()));
int k = path.indexOf(pnames[0]);
if (path.lastIndexOf(sep, k) != -1) {
pwd.append('/');
markup.append(sep);
}
for (int i = 0; i < pnames.length; i++ ) {
pwd.append(URIEncodePath(pnames[i]));
if (isDir || i < pnames.length - 1) {
pwd.append('/');
}
markup.append(anchorLinkStart).append(prefix).append(pwd)
.append(postfix).append(closeQuotedTag).append(pnames[i])
.append(anchorEnd);
if (isDir || i < pnames.length - 1) {
markup.append(sep);
}
}
return markup.toString();
}
/**
* Normalize the given <var>path</var> to its canonical form. I.e. all
* separators (<var>sep</var>) are replaced with a slash ('/'), all
* double slashes are replaced by a single slash, all single dot path
* components (".") of the formed path are removed and all double dot path
* components (".." ) of the formed path are replaced with its parent or
* '/' if there is no parent.
* <p>
* So the difference to {@link File#getCanonicalPath()} is, that this method
* does not hit the disk (just string manipulation), resolves <var>path</var>
* always against '/' and thus always returns an absolute path, which may
* actually not exist, and which has a single trailing '/' if the given
* <var>path</var> ends with the given <var>sep</var>.
*
* @param path path to mangle. If not absolute or {@code null}, the
* current working directory is assumed to be '/'.
* @param sep file separator to use to crack <var>path</var> into path
* components
* @return always a canonical path which starts with a '/'.
*/
public static String getCanonicalPath(String path, char sep) {
if (path == null || path.length() == 0) {
return "/";
}
String[] pnames = normalize(path.split(escapeForRegex(sep)), true);
if (pnames.length == 0) {
return "/";
}
StringBuilder buf = new StringBuilder(path.length());
buf.append('/');
for (int i=0; i < pnames.length; i++) {
buf.append(pnames[i]).append('/');
}
if (path.charAt(path.length()-1) != sep) {
// since is not a general purpose method. So we waive to handle
// cases like:
// || path.endsWith("/..") || path.endsWith("/.")
buf.setLength(buf.length()-1);
}
return buf.toString();
}
private final static Pattern EMAIL_PATTERN =
Pattern.compile("([^<\\s]+@[^>\\s]+)");
/**
* Get email address of the author.
*
* @param author
* string containing author and possibly email address.
* @return email address of the author or
* full author string if the author string does not contain an email
* address.
*/
public static String getEmail(String author) {
Matcher email_matcher = EMAIL_PATTERN.matcher(author);
String email = author;
if (email_matcher.find()) {
email = email_matcher.group(1).trim();
}
return email;
}
/**
* Remove all empty and {@code null} string elements from the given
* <var>names</var> and optionally all redundant information like "." and
* "..".
*
* @param names
* names to check
* @param canonical
* if {@code true}, remove redundant elements as well.
* @return a possible empty array of names all with a length &gt; 0.
*/
private static String[] normalize(String[] names, boolean canonical) {
LinkedList<String> res = new LinkedList<String>();
if (names == null || names.length == 0) {
return new String[0];
}
for (int i = 0; i < names.length; i++ ) {
if (names[i] == null || names[i].length() == 0) {
continue;
}
if (canonical) {
if (names[i].equals("..")) {
if (!res.isEmpty()) {
res.removeLast();
}
} else if (names[i].equals(".")) {
continue;
} else {
res.add(names[i]);
}
} else {
res.add(names[i]);
}
}
return res.size() == names.length ? names : res.toArray(new String[res
.size()]);
}
/**
* 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(c);
for (int i = 0; i < 4 - hex.length(); i++ ) {
sb.append('0');
}
sb.append(hex);
return sb.toString();
}
private static NumberFormat FORMATTER = new DecimalFormat("#,###,###,###.#");
/**
* Convert the given size into a human readable string.
* @param num size to convert.
* @return a readable string
*/
public static String readableSize(long num) {
float l = num;
NumberFormat formatter = (NumberFormat) FORMATTER.clone();
if (l < 1024) {
return formatter.format(l) + ' '; // for none-dirs append 'B'? ...
} else if (l < 1048576) {
return (formatter.format(l / 1024) + " KiB");
} else {
return ("<b>" + formatter.format(l / 1048576) + " MiB</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) {
StringBuilder sb = new StringBuilder();
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("&lt;br/&gt;");
break;
case '\r':
break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
/**
* Write out line information wrt. to the given annotation in the format:
* {@code Linenumber Blame Author} incl. appropriate links.
*
* @param num linenumber to print
* @param out print destination
* @param annotation annotation to use. If {@code null} only the
* linenumber gets printed.
* @param userPageLink see {@link RuntimeEnvironment#getUserPage()}
* @param userPageSuffix see {@link RuntimeEnvironment#getUserPageSuffix()}
* @throws IOException depends on the destination (<var>out</var>).
*/
public static void readableLine(int num, Writer out, Annotation annotation,
String userPageLink, String userPageSuffix)
throws IOException
{
// this method should go to JFlexXref
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(snum);
out.write(anchorEnd);
if (annotation != null) {
String r = annotation.getRevision(num);
boolean enabled = annotation.isEnabled(num);
out.write("<span class=\"blame\">");
if (enabled) {
out.write(anchorClassStart);
out.write("r\" href=\"" );
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("\" title=\"");
out.write(msg);
}
out.write(closeQuotedTag);
}
StringBuilder buf = new StringBuilder();
htmlize(r, buf);
out.write(buf.toString());
buf.setLength(0);
if (enabled) {
out.write(anchorEnd);
}
String a = annotation.getAuthor(num);
if (userPageLink == null) {
out.write("<span class=\"a\">");
htmlize(a, buf);
out.write(buf.toString());
out.write("</span>");
buf.setLength(0);
} else {
out.write(anchorClassStart);
out.write("a\" href=\"");
out.write(userPageLink);
out.write(URIEncode(a));
if (userPageSuffix != null) {
out.write(userPageSuffix);
}
out.write(closeQuotedTag);
htmlize(a, buf);
out.write(buf.toString());
buf.setLength(0);
out.write(anchorEnd);
}
out.write("</span>");
}
}
/**
* Generate a string from the given path and date in a way that allows
* stable lexicographic sorting (i.e. gives always the same results) as a
* walk of the file hierarchy. Thus null character (\u0000) is used both
* to separate directory components and to separate the path from the date.
* @param path path to mangle.
* @param date date string to use.
* @return the mangled path.
*/
public static String path2uid(String path, String date) {
return path.replace('/', '\u0000') + "\u0000" + date;
}
/**
* The reverse operation for {@link #path2uid(String, String)} - re-creates
* the unmangled path from the given uid.
* @param uid uid to unmangle.
* @return the original path.
*/
public static String uid2url(String uid) {
String url = uid.replace('\u0000', '/');
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. If {@code null}, an empty string will
* be used instead.
* @return null if fail, otherwise the encoded string
* @see URLEncoder#encode(String, String)
*/
public static String URIEncode(String q) {
try {
return q == null ? "" : URLEncoder.encode(q, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Should not happen. UTF-8 must be supported by JVMs.
Logger.getLogger(EftarFileReader.class.getName()).log(Level.WARNING, "Failed to URL-encode UTF-8: ", e);
}
return null;
}
/**
* Append '&amp;name=value" to the given buffer. If the given <var>value</var>
* is {@code null}, this method does nothing.
*
* @param buf where to append the query string
* @param key the name of the parameter to add. Append as is!
* @param value the value for the given parameter. Gets automatically UTF-8
* URL encoded.
* @throws NullPointerException if the given buffer is {@code null}.
* @see #URIEncode(String)
*/
public static void appendQuery(StringBuilder buf, String key,
String value)
{
if (value != null) {
buf.append("&amp;").append(key).append('=').append(URIEncode(value));
}
}
/**
* URI encode the given path.
* @param path path to encode.
* @return the encoded path.
* @see URI#getRawPath()
* @throws NullPointerException if a parameter is {@code 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 "";
}
/**
* Replace all quote characters (ASCI 0x22) with the corresponding html
* entity (&amp;quot;).
* @param q string to escape.
* @return an empty string if a parameter is {@code null}, the mangled
* string otherwise.
*/
public static String formQuoteEscape(String q) {
if (q == null || q.isEmpty()) {
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();
}
private static final String SPAN_D = "<span class=\"d\">";
private static final String SPAN_A = "<span class=\"a\">";
private static final String SPAN_E = "</span>";
private static final int SPAN_LEN = SPAN_D.length() + SPAN_E.length();
/**
* Tag changes in the given <var>line1</var> and <var>line2</var>
* for highlighting. Removed parts are tagged with CSS class {@code d},
* new parts are tagged with CSS class {@code a} using a {@code span}
* element.
*
* @param line1 line of the original file
* @param line2 line of the changed/new file
* @return the tagged lines (field[0] ~= line1, field[1] ~= line2).
* @throws NullPointerException if one of the given parameters is {@code null}.
*/
public static String[] diffline(StringBuilder line1, StringBuilder line2) {
int m = line1.length();
int n = line2.length();
if (n == 0 || m == 0) {
return new String[] {line1.toString(), line2.toString()};
}
int s = 0;
char[] csl1 = new char[m + SPAN_LEN];
line1.getChars(0, m--, csl1, 0);
char[] csl2 = new char[n + SPAN_LEN];
line2.getChars(0, n--, csl2, 0);
while (s <= m && s <= n && csl1[s] == csl2[s]) {
s++ ;
}
while (s <= m && s <= n && csl1[m] == csl2[n]) {
m-- ;
n-- ;
}
String[] ret = new String[2];
// deleted
if (s <= m) {
m++;
System.arraycopy(csl1, m, csl1, m + SPAN_LEN, line1.length() - m);
System.arraycopy(csl1, s, csl1, s + SPAN_D.length(), m - s);
SPAN_E.getChars(0, SPAN_E.length(), csl1, m + SPAN_D.length());
SPAN_D.getChars(0, SPAN_D.length(), csl1, s);
ret[0] = new String(csl1);
} else {
ret[0] = line1.toString();
}
// added
if (s <= n) {
n++;
System.arraycopy(csl2, n, csl2, n + SPAN_LEN, line2.length() - n);
System.arraycopy(csl2, s, csl2, s + SPAN_A.length(), n - s);
SPAN_E.getChars(0, SPAN_E.length(), csl2, n + SPAN_A.length());
SPAN_A.getChars(0, SPAN_A.length(), csl2, s);
ret[1] = new String(csl2);
} else {
ret[1] = line2.toString();
}
return ret;
}
/**
* Dump the configuration as an HTML table.
*
* @param out
* destination for the HTML output
* @throws IOException
* if an error happens while writing to {@code out}
* @throws HistoryException
* if the history guru cannot be accesses
*/
@SuppressWarnings("boxing")
public static void dumpConfiguration(Appendable out) throws IOException,
HistoryException
{
out.append("<table border=\"1\" width=\"100%\">");
out.append("<tr><th>Variable</th><th>Value</th></tr>");
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
printTableRow(out, "Source root", env.getSourceRootPath());
printTableRow(out, "Data root", env.getDataRootPath());
printTableRow(out, "CTags", env.getCtags());
printTableRow(out, "Bug page", env.getBugPage());
printTableRow(out, "Bug pattern", env.getBugPattern());
printTableRow(out, "User page", env.getUserPage());
printTableRow(out, "User page suffix", env.getUserPageSuffix());
printTableRow(out, "Review page", env.getReviewPage());
printTableRow(out, "Review pattern", env.getReviewPattern());
printTableRow(out, "Using projects", env.hasProjects());
out.append("<tr><td>Ignored files</td><td>");
printUnorderedList(out, env.getIgnoredNames().getItems());
out.append("</td></tr>");
printTableRow(out, "Index word limit", env.getIndexWordLimit());
printTableRow(out, "Allow leading wildcard in search",
env.isAllowLeadingWildcard());
printTableRow(out, "History cache", HistoryGuru.getInstance()
.getCacheInfo());
out.append("</table>");
}
/**
* Just read the given source and dump as is to the given destionation.
* Does nothing, if one or more of the parameters is {@code null}.
* @param out write destination
* @param in source to read
* @throws IOException as defined by the given reader/writer
* @throws NullPointerException if a parameter is {@code null}.
*/
public static void dump(Writer out, Reader in) throws IOException {
if (in == null || out == null) {
return;
}
char[] buf = new char[8192];
int len = 0;
while ((len = in.read(buf)) >= 0) {
out.write(buf, 0, len);
}
}
/**
* Silently dump a file to the given destionation. All {@link IOException}s
* gets caught and logged, but not re-thrown.
*
* @param out dump destination
* @param dir directory, which should contains the file.
* @param filename the basename of the file to dump.
* @param compressed if {@code true} the denoted file is assumed to be
* gzipped.
* @return {@code true} on success (everything read and written).
* @throws NullPointerException if a parameter is {@code null}.
*/
public static boolean dump(Writer out, File dir, String filename,
boolean compressed)
{
return dump(out, new File(dir, filename), compressed);
}
/**
* Silently dump a file to the given destionation. All {@link IOException}s
* gets caught and logged, but not re-thrown.
*
* @param out dump destination
* @param file file to dump.
* @param compressed if {@code true} the denoted file is assumed to be
* gzipped.
* @return {@code true} on success (everything read and written).
* @throws NullPointerException if a parameter is {@code null}.
*/
public static boolean dump(Writer out, File file, boolean compressed) {
if (!file.exists()) {
return false;
}
FileInputStream fis = null;
GZIPInputStream gis = null;
Reader in = null;
try {
if (compressed) {
fis = new FileInputStream(file);
gis = new GZIPInputStream(fis);
in = new InputStreamReader(gis);
} else {
in = new FileReader(file);
}
dump(out, in);
return true;
} catch(IOException e) {
OpenGrokLogger.getLogger().log(Level.WARNING,
"An error occured while piping file " + file + ": ", e);
} finally {
IOUtils.close(in);
IOUtils.close(gis);
IOUtils.close(fis);
}
return false;
}
/**
* Print a row in an HTML table.
*
* @param out
* destination for the HTML output
* @param cells
* the values to print in the cells of the row
* @throws IOException
* if an error happens while writing to {@code out}
*/
private static void printTableRow(Appendable out, Object... cells)
throws IOException
{
out.append("<tr>");
StringBuilder buf = new StringBuilder(256);
for (Object cell : cells) {
out.append("<td>");
String str = (cell == null) ? "null" : cell.toString();
htmlize(str, buf);
out.append(str);
buf.setLength(0);
out.append("</td>");
}
out.append("</tr>");
}
/**
* Print an unordered list (HTML).
*
* @param out
* destination for the HTML output
* @param items
* the list items
* @throws IOException
* if an error happens while writing to {@code out}
*/
private static void printUnorderedList(Appendable out,
Collection<String> items) throws IOException
{
out.append("<ul>");
StringBuilder buf = new StringBuilder(256);
for (String item : items) {
out.append("<li>");
htmlize(item, buf);
out.append(buf);
buf.setLength(0);
out.append("</li>");
}
out.append("</ul>");
}
/**
* Create a string literal for use in JavaScript functions.
*
* @param str
* the string to be represented by the literal
* @return a JavaScript string literal
*/
public static String jsStringLiteral(String str) {
StringBuilder sb = new StringBuilder();
sb.append('"');
for (int i = 0; i < str.length(); i++ ) {
char c = str.charAt(i);
switch (c) {
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
default:
sb.append(c);
}
}
sb.append('"');
return sb.toString();
}
}