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