Clover coverage report - DrJava Test Coverage (drjava-20120422-r5456)
Coverage timestamp: Sun Apr 22 2012 03:13:25 CDT
file stats: LOC: 1,051   Methods: 35
NCLOC: 714   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
StringOps.java 80.1% 86.5% 88.6% 84.8%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.util;
 38   
 39    import edu.rice.cs.plt.tuple.Pair;
 40    import edu.rice.cs.plt.lambda.Lambda2;
 41    import edu.rice.cs.plt.lambda.Lambda;
 42    import edu.rice.cs.drjava.config.*;
 43    import java.io.StringWriter;
 44    import java.io.PrintWriter;
 45    import java.text.DecimalFormat;
 46    import java.io.StringReader;
 47    import java.util.ArrayList;
 48    import java.util.List;
 49    import java.util.HashMap;
 50    import java.io.IOException;
 51    import java.io.*;
 52    import javax.swing.text.html.*;
 53    import javax.swing.text.html.parser.*;
 54   
 55    /**
 56    * A class to provide some convenient String operations as static methods.
 57    * It's abstract to prevent (useless) instantiation, though it can be subclassed
 58    * to provide convenient namespace importation of its methods.
 59    * @version $Id: StringOps.java 5408 2010-11-16 06:52:07Z mgricken $
 60    */
 61   
 62    public abstract class StringOps {
 63   
 64    public static final String EOL = System.getProperty("line.separator");
 65    public static final String NEWLINE = "\n";
 66    public static final char newline = '\n';
 67    public static final char SEPARATOR = '\u220E';
 68   
 69    /** Defines blank[k] (k = 0,..,16) as a string consisting of k blanks */
 70    private static final String blank0 = "";
 71    private static final String blank1 = makeBlankString(1);
 72    private static final String blank2 = makeBlankString(2);
 73    private static final String blank3 = makeBlankString(3);
 74    private static final String blank4 = makeBlankString(4);
 75    private static final String blank5 = makeBlankString(5);
 76    private static final String blank6 = makeBlankString(6);
 77    private static final String blank7 = makeBlankString(7);
 78    private static final String blank8 = makeBlankString(8);
 79    private static final String blank9 = makeBlankString(9);
 80    private static final String blank10 = makeBlankString(10);
 81    private static final String blank11 = makeBlankString(11);
 82    private static final String blank12 = makeBlankString(12);
 83    private static final String blank13 = makeBlankString(13);
 84    private static final String blank14 = makeBlankString(14);
 85    private static final String blank15 = makeBlankString(15);
 86    private static final String blank16 = makeBlankString(16);
 87   
 88    /** Gets a string consisting of n blanks. The values for n <= 16 are stored in a switch table.*/
 89  179 public static String getBlankString(int n) {
 90  179 switch (n) {
 91  0 case 0: return blank0;
 92  6 case 1: return blank1;
 93  77 case 2: return blank2;
 94  11 case 3: return blank3;
 95  46 case 4: return blank4;
 96  4 case 5: return blank5;
 97  16 case 6: return blank6;
 98  0 case 7: return blank7;
 99  1 case 8: return blank8;
 100  0 case 9: return blank9;
 101  1 case 10: return blank10;
 102  3 case 11: return blank11;
 103  7 case 12: return blank12;
 104  1 case 13: return blank13;
 105  2 case 14: return blank14;
 106  2 case 15: return blank15;
 107  0 case 16: return blank16;
 108  2 default:
 109  2 return makeBlankString(n);
 110    }
 111    }
 112   
 113    /** Constructs a new string containng n blanks. Intended for small values of n (typically < 50). */
 114  642 private static String makeBlankString(int n) {
 115  642 StringBuilder buf = new StringBuilder();
 116  5477 for (int i = 0; i < n; i++) buf.append(' ');
 117  642 return buf.toString();
 118    }
 119   
 120    /** Takes theString fullString and replaces all instances of toReplace with replacement.
 121    * TODO: deprecate and used corresponding String method added in Java 5.0.
 122    */
 123  1072 public static String replace (String fullString, String toReplace, String replacement) {
 124  1072 int index = 0;
 125  1072 int pos;
 126  1072 int fullStringLength = fullString.length();
 127  1072 int toReplaceLength = toReplace.length();
 128  1072 if (toReplaceLength > 0) {
 129  1072 int replacementLength = replacement.length();
 130  1072 StringBuilder buff;
 131  ? while (index < fullStringLength &&
 132    ((pos = fullString.indexOf(toReplace, index)) >= 0)) {
 133  229 buff = new StringBuilder(fullString.substring(0, pos));
 134  229 buff.append(replacement);
 135  229 buff.append(fullString.substring(pos + toReplaceLength, fullStringLength));
 136  229 index = pos + replacementLength;
 137  229 fullString = buff.toString();
 138  229 fullStringLength = fullString.length();
 139    }
 140    }
 141  1072 return fullString;
 142    }
 143   
 144    /** Converts the given string to a valid Java string literal.
 145    * All back slashes, quotes, new-lines, and tabs are converted
 146    * to their escap character form, and the sourounding quotes
 147    * are added.
 148    * @param s the normal string to turn into a string literal
 149    * @return the valid Java string literal
 150    */
 151  134 public static String convertToLiteral(String s) {
 152  134 String output = s;
 153  134 output = replace(output, "\\", "\\\\"); // convert \ to \\
 154  134 output = replace(output, "\"", "\\\""); // convert " to \"
 155  134 output = replace(output, "\t", "\\t"); // convert [tab] to \t
 156  134 output = replace(output, "\n", "\\n"); // convert [newline] to \n
 157  134 return "\"" + output + "\"";
 158    }
 159   
 160    /** Verifies that (startRow, startCol) occurs before (endRow, endCol).
 161    * @throws IllegalArgumentException if end is before start
 162    */
 163  11 private static void _ensureStartBeforeEnd(int startRow, int startCol,
 164    int endRow, int endCol) {
 165  11 if (startRow > endRow) {
 166  1 throw new IllegalArgumentException("end row before start row: " +
 167    startRow + " > " + endRow);
 168    }
 169  10 else if (startRow == endRow && startCol > endCol) {
 170  1 throw new IllegalArgumentException("end before start: (" +
 171    startRow + ", " + startCol +
 172    ") > (" + endRow + ", " + endCol + ")");
 173    }
 174    }
 175   
 176    /** Verifies that the given column position is within the row at rowStartIndex
 177    * in the given String.
 178    * @param fullString the string in which to check the column
 179    * @param col the column index that should be within the row
 180    * @param rowStartIndex the first index of the row within fullString that col should be in
 181    * @throws IllegalArgumentException if col is after the end of the given row
 182    */
 183  16 private static void _ensureColInRow(String fullString, int col, int rowStartIndex) {
 184  16 int endOfLine = fullString.indexOf(NEWLINE,rowStartIndex);
 185  16 if (endOfLine == -1) {
 186  1 endOfLine = fullString.length();
 187    }
 188  16 if (col > (endOfLine - rowStartIndex)) {
 189  3 throw new IllegalArgumentException("the given column is past the end of its row");
 190    }
 191    }
 192   
 193    /** Gets the offset and length equivalent to the given pairs start and end row-col.
 194    * @param fullString the string in which to compute the offset/length
 195    * @param startRow the row on which the error starts, starting at one for the first row
 196    * @param startCol the col on which the error starts, starting at one for the first column
 197    * @param endRow the row on which the error ends. Equals the startRow for one-line errors
 198    * @param endCol the character position on which the error ends. Equals the startCol for one-character errors.
 199    * @return a Pair of which the first is the offset, the second is the length
 200    */
 201  11 public static Pair<Integer, Integer> getOffsetAndLength(String fullString, int startRow,
 202    int startCol, int endRow, int endCol) {
 203  11 _ensureStartBeforeEnd(startRow, startCol, endRow, endCol);
 204   
 205    // find the offset
 206  9 int currentChar = 0;
 207  9 int linesSeen = 1;
 208  9 while (startRow > linesSeen) {
 209  9 currentChar = fullString.indexOf(NEWLINE,currentChar);
 210  9 if (currentChar == -1) {
 211  0 throw new IllegalArgumentException("startRow is beyond the end of the string");
 212    }
 213    // Must move past the newline
 214  9 currentChar++;
 215  9 linesSeen++;
 216    }
 217   
 218  9 _ensureColInRow(fullString, startCol, currentChar);
 219  7 int offset = currentChar + startCol - 1; // offset is zero-based
 220   
 221    // find the length
 222  7 while (endRow > linesSeen) {
 223  3 currentChar = fullString.indexOf(NEWLINE, currentChar);
 224  3 if (currentChar == -1) {
 225  0 throw new IllegalArgumentException("endRow is beyond the end of the string");
 226    }
 227  3 currentChar++;
 228  3 linesSeen++;
 229    }
 230   
 231  7 _ensureColInRow(fullString, endCol, currentChar);
 232  6 int length = currentChar + endCol - offset;
 233   
 234    // ensure the length is in bounds
 235  6 if (offset + length > fullString.length()) {
 236  0 throw new IllegalArgumentException("Given positions beyond the end of the string");
 237    }
 238  6 return new Pair<Integer, Integer>(Integer.valueOf(offset), Integer.valueOf(length));
 239    }
 240   
 241    /** Gets the stack trace of the given Throwable as a String.
 242    * @param t the throwable object for which to get the stack trace
 243    * @return the stack trace of the given Throwable
 244    */
 245  1 public static String getStackTrace(Throwable t) {
 246  1 StringWriter sw = new StringWriter();
 247  1 PrintWriter pw = new PrintWriter(sw);
 248  1 t.printStackTrace(pw);
 249  1 return sw.toString();
 250    }
 251   
 252    /** Gets the stack trace of the current code. Does not include this method.
 253    * @return the stack trace for the current code
 254    */
 255  0 public static String getStackTrace() {
 256  0 try { throw new Exception(); } // Thread.getStackTrace() might be more efficient, but is new in Java 5.0
 257    catch (Exception e) {
 258  0 StringWriter sw = new StringWriter();
 259  0 PrintWriter pw = new PrintWriter(sw);
 260  0 StackTraceElement[] stes = e.getStackTrace();
 261  0 int skip = 1;
 262  0 for(StackTraceElement ste: stes) {
 263  0 if (skip > 0) { --skip; } else { pw.print("at "); pw.println(ste); }
 264    }
 265  0 return sw.toString();
 266    }
 267    }
 268   
 269    /** Character.isDigit answers <tt>true</tt> to some non-ascii
 270    * digits. This one does not.
 271    */
 272  9 public static boolean isAsciiDigit(char c) {
 273  9 return '0' <= c && c <= '9';
 274    }
 275   
 276    /** Returns true if the class is an anonymous inner class.
 277    * This works just like Class.isAnonymousClass() in Java 5.0 but is not version-specific.
 278    * @param c class to check
 279    * @return true if anonymous inner class
 280    */
 281  10 public static boolean isAnonymousClass(Class<?> c) {
 282  10 String simpleName = c.getName();
 283  10 int idx = simpleName.lastIndexOf('$');
 284  10 if (idx >= 0) {
 285    // see if we have just numbers after the $
 286  9 for (int pos=idx+1; pos < simpleName.length(); ++pos) {
 287  9 if (!isAsciiDigit(simpleName.charAt(pos))) {
 288  4 return false;
 289    }
 290    }
 291  5 return true;
 292    }
 293  1 return false;
 294    }
 295   
 296    /** Returns true if the class is a member class.
 297    * This works just like Class.isMemberClass() in Java 5.0 but is not version-specific.
 298    * @param c class to check
 299    * @return true if member class
 300    */
 301  2 public static boolean isMemberClass(Class<?> c) {
 302  2 String simpleName = c.getName();
 303  2 int idx = simpleName.lastIndexOf('$');
 304  2 if (idx == -1) {
 305  1 return false;
 306    }
 307  1 return !isAnonymousClass(c);
 308    }
 309   
 310    /** Returns the simple class name.
 311    * This works just like Class.getSimpleName() in Java 5.0 but is not version-specific.
 312    * @param c class for which to get the simple name
 313    * @return simple name
 314    */
 315  9 public static String getSimpleName(Class<?> c) {
 316  9 if (c.isArray())
 317  0 return getSimpleName(c.getComponentType()) + "[]";
 318   
 319  9 if (isAnonymousClass(c)) {
 320  5 return "";
 321    }
 322   
 323  4 String simpleName = c.getName();
 324  4 int idx = Math.max(simpleName.lastIndexOf('.'),
 325    simpleName.lastIndexOf('$'));
 326  4 return simpleName.substring(idx + 1); // strip the package name
 327    }
 328   
 329    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific.
 330    */
 331  4 public static String toString(long[] a) {
 332  4 if (a == null)
 333  1 return "null";
 334  3 if (a.length == 0)
 335  1 return "[]";
 336   
 337  2 final StringBuilder buf = new StringBuilder();
 338  2 buf.append('[');
 339  2 buf.append(a[0]);
 340   
 341  2 for (int i = 1; i < a.length; i++) {
 342  1 buf.append(", ");
 343  1 buf.append(a[i]);
 344    }
 345   
 346  2 buf.append("]");
 347  2 return buf.toString();
 348    }
 349   
 350    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific. */
 351  4 public static String toString(int[] a) {
 352  4 if (a == null)
 353  1 return "null";
 354  3 if (a.length == 0)
 355  1 return "[]";
 356   
 357  2 final StringBuilder buf = new StringBuilder();
 358  2 buf.append('[');
 359  2 buf.append(a[0]);
 360   
 361  2 for (int i = 1; i < a.length; i++) {
 362  1 buf.append(", ");
 363  1 buf.append(a[i]);
 364    }
 365   
 366  2 buf.append("]");
 367  2 return buf.toString();
 368    }
 369   
 370    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific. */
 371  4 public static String toString(short[] a) {
 372  4 if (a == null)
 373  1 return "null";
 374  3 if (a.length == 0)
 375  1 return "[]";
 376   
 377  2 final StringBuilder buf = new StringBuilder();
 378  2 buf.append('[');
 379  2 buf.append(a[0]);
 380   
 381  2 for (int i = 1; i < a.length; i++) {
 382  1 buf.append(", ");
 383  1 buf.append(a[i]);
 384    }
 385   
 386  2 buf.append("]");
 387  2 return buf.toString();
 388    }
 389   
 390    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific.
 391    */
 392  4 public static String toString(char[] a) {
 393  4 if (a == null)
 394  1 return "null";
 395  3 if (a.length == 0)
 396  1 return "[]";
 397   
 398  2 final StringBuilder buf = new StringBuilder();
 399  2 buf.append('[');
 400  2 buf.append(a[0]);
 401   
 402  2 for (int i = 1; i < a.length; i++) {
 403  1 buf.append(", ");
 404  1 buf.append(a[i]);
 405    }
 406   
 407  2 buf.append("]");
 408  2 return buf.toString();
 409    }
 410   
 411    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific.
 412    */
 413  4 public static String toString(byte[] a) {
 414  4 if (a == null)
 415  1 return "null";
 416  3 if (a.length == 0)
 417  1 return "[]";
 418   
 419  2 final StringBuilder buf = new StringBuilder();
 420  2 buf.append('[');
 421  2 buf.append(a[0]);
 422   
 423  2 for (int i = 1; i < a.length; i++) {
 424  1 buf.append(", ");
 425  1 buf.append(a[i]);
 426    }
 427   
 428  2 buf.append("]");
 429  2 return buf.toString();
 430    }
 431   
 432    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific.
 433    */
 434  4 public static String toString(boolean[] a) {
 435  4 if (a == null)
 436  1 return "null";
 437  3 if (a.length == 0)
 438  1 return "[]";
 439   
 440  2 final StringBuilder buf = new StringBuilder();
 441  2 buf.append('[');
 442  2 buf.append(a[0]);
 443   
 444  2 for (int i = 1; i < a.length; i++) {
 445  1 buf.append(", ");
 446  1 buf.append(a[i]);
 447    }
 448   
 449  2 buf.append("]");
 450  2 return buf.toString();
 451    }
 452   
 453    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific.
 454    */
 455  4 public static String toString(float[] a) {
 456  4 if (a == null)
 457  1 return "null";
 458  3 if (a.length == 0)
 459  1 return "[]";
 460   
 461  2 final StringBuilder buf = new StringBuilder();
 462  2 buf.append('[');
 463  2 buf.append(a[0]);
 464   
 465  2 for (int i = 1; i < a.length; i++) {
 466  1 buf.append(", ");
 467  1 buf.append(a[i]);
 468    }
 469   
 470  2 buf.append("]");
 471  2 return buf.toString();
 472    }
 473   
 474    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific.
 475    */
 476  4 public static String toString(double[] a) {
 477  4 if (a == null)
 478  1 return "null";
 479  3 if (a.length == 0)
 480  1 return "[]";
 481   
 482  2 final StringBuilder buf = new StringBuilder();
 483  2 buf.append('[');
 484  2 buf.append(a[0]);
 485   
 486  2 for (int i = 1; i < a.length; i++) {
 487  1 buf.append(", ");
 488  1 buf.append(a[i]);
 489    }
 490   
 491  2 buf.append("]");
 492  2 return buf.toString();
 493    }
 494   
 495    /** This works just like java.util.Arrays.toString in Java 5.0 but is not version-specific.
 496    */
 497  4 public static String toString(Object[] a) {
 498  4 if (a == null)
 499  1 return "null";
 500  3 if (a.length == 0)
 501  1 return "[]";
 502   
 503  2 final StringBuilder buf = new StringBuilder();
 504   
 505  2 for (int i = 0; i < a.length; i++) {
 506  3 if (i == 0)
 507  2 buf.append('[');
 508    else
 509  1 buf.append(", ");
 510   
 511  3 buf.append(String.valueOf(a[i]));
 512    }
 513   
 514  2 buf.append("]");
 515  2 return buf.toString();
 516    }
 517   
 518    /** Encode &, <, > and newlines as HTML entities.
 519    * @param s string to encode
 520    * @return encoded string
 521    */
 522  13 public static String encodeHTML(String s) {
 523  13 s = StringOps.replace(s, "&", "&amp;");
 524  13 s = StringOps.replace(s, "<", "&lt;");
 525  13 s = StringOps.replace(s, ">", "&gt;");
 526  13 s = StringOps.replace(s, EOL,"<br>");
 527  13 s = StringOps.replace(s, NEWLINE,"<br>");
 528  13 return s;
 529    }
 530   
 531    /* Eliminates extra whitespace characters. */
 532  21 public static String compress(String s) {
 533  21 int len = s.length();
 534  21 boolean inWSGap = false;
 535  21 StringBuilder sb = new StringBuilder(len);
 536  21 for (int i = 0; i < len; i++) {
 537  98 char ch = s.charAt(i);
 538  98 if (Character.isWhitespace(ch)) {
 539  59 if (! inWSGap) {
 540  25 inWSGap = true;
 541  25 sb.append(ch);
 542    }
 543    }
 544    else {
 545  39 inWSGap = false;
 546  39 sb.append(ch);
 547    }
 548    }
 549  21 return sb.toString();
 550    }
 551   
 552    /** Converts newline chars to SEPARATOR char (a solid black upright rectangle). */
 553  6 public static String flatten(String s) { return s.replace(newline, SEPARATOR); }
 554   
 555    /** Return a string representing the approximate amount of memory specified in bytes.
 556    * @param l memory in bytes
 557    * @return string approximating the amount of memory
 558    */
 559  163 public static String memSizeToString(long l) {
 560  163 String[] sizes = new String[] { "byte", "kilobyte", "megabyte", "gigabyte" };
 561  163 double d = l;
 562  163 int i = 0;
 563  163 while((d >= 1024) && (i < sizes.length)) {
 564  327 ++i;
 565  327 d /= 1024;
 566    }
 567  2 if (i >= sizes.length) { i = sizes.length - 1; d *= 1024; /* undo last division by 1024 */ }
 568  163 StringBuilder sb = new StringBuilder();
 569  163 long whole = (long)d;
 570  163 if (whole == d) {
 571  8 if (whole == 1) {
 572  3 sb.append(whole);
 573  3 sb.append(' ');
 574  3 sb.append(sizes[i]);
 575    }
 576    else {
 577  5 sb.append(whole);
 578  5 sb.append(' ');
 579  5 sb.append(sizes[i]);
 580  5 sb.append('s');
 581    }
 582    }
 583    else {
 584    // two decimal digits
 585  155 DecimalFormat df = new DecimalFormat("#.00");
 586  155 sb.append(df.format(d));
 587  155 sb.append(' ');
 588  155 sb.append(sizes[i]);
 589  155 sb.append('s');
 590    }
 591  163 return sb.toString();
 592    }
 593   
 594    // public static edu.rice.cs.util.Log LOG = new edu.rice.cs.util.Log("stringops.txt", false);
 595   
 596    /** Escapes spaces ' ' with the sequence "\u001b ", and a single '\u001b' with a double.
 597    * It treats File.pathSeparatorChar (';' or ':'), ProcessChain.PROCESS_SEPARATOR_CHAR ('#'),
 598    * ProcessChain.PIPE_SEPARATOR_CHAR, and ':' (for Windows drive letters) the same way.
 599    * '\u001b' was picked because its ASCII meaning is 'escape', and it should be platform-independent.
 600    * This method keeps file names with spaces, pound, colons and semicolons together and prevents them
 601    * from being split apart.
 602    * @param s string to encode
 603    * @return encoded string */
 604  245 public static String escapeFileName(String s) {
 605  245 StringBuilder sb = new StringBuilder();
 606  245 for (int i = 0; i < s.length(); ++i) {
 607  7713 if (s.charAt(i) == '\u001b') {
 608  6 sb.append("\u001b\u001b");
 609    }
 610  7707 else if (s.charAt(i) == ' ') {
 611  34 sb.append("\u001b ");
 612    }
 613  7673 else if (s.charAt(i) == java.io.File.pathSeparatorChar) {
 614  10 sb.append('\u001b');
 615  10 sb.append(java.io.File.pathSeparatorChar);
 616    }
 617  7663 else if (s.charAt(i) == ProcessChain.PROCESS_SEPARATOR_CHAR) {
 618  5 sb.append('\u001b');
 619  5 sb.append(ProcessChain.PROCESS_SEPARATOR_CHAR);
 620    }
 621  7658 else if (s.charAt(i) == ProcessChain.PIPE_SEPARATOR_CHAR) {
 622  5 sb.append('\u001b');
 623  5 sb.append(ProcessChain.PIPE_SEPARATOR_CHAR);
 624    }
 625  7653 else if (s.charAt(i) == ':') {
 626  0 sb.append("\u001b:"); // for Windows, escape the : in drive letters
 627    // on Unix, this case is irrelevant, since pathSeparatorChar==':'
 628    }
 629    else {
 630  7653 sb.append(String.valueOf(s.charAt(i)));
 631    }
 632    }
 633  245 return sb.toString();
 634    }
 635   
 636    /** Unescapes spaces the sequence "\u001b " to a space ' ', and a double '\u001b' to a single.
 637    * '\u001b' was picked because its ASCII meaning is 'escape', and it should be platform-independent.
 638    * @param s string to encode
 639    * @return encoded string */
 640  294 public static String unescapeFileName(String s) {
 641  294 StringBuilder sb = new StringBuilder();
 642  294 for (int i = 0; i < s.length(); ++i) {
 643  5064 if (s.charAt(i) == '\u001b') {
 644  31 if (i+1<s.length()) {
 645  31 char next = s.charAt(i+1);
 646  6 if (next=='\u001b') { sb.append("\u001b"); ++i; }
 647  5 else if (next==' ') { sb.append(" "); ++i; }
 648  10 else if (next==java.io.File.pathSeparatorChar) { sb.append(java.io.File.pathSeparatorChar); ++i; }
 649  5 else if (next==ProcessChain.PROCESS_SEPARATOR_CHAR) { sb.append(ProcessChain.PROCESS_SEPARATOR_CHAR); ++i; }
 650  5 else if (next==ProcessChain.PIPE_SEPARATOR_CHAR) { sb.append(ProcessChain.PIPE_SEPARATOR_CHAR); ++i; }
 651  0 else if (next==':') { sb.append(':'); ++i; }
 652  0 else { throw new IllegalArgumentException("1b hex followed by character other than space, " +
 653    "path separator, process separator, pipe, colon, or 1b hex"); }
 654    }
 655  0 else { throw new IllegalArgumentException("1b hex followed by character other than space, " +
 656    "path separator, process separator, pipe, colon, or 1b hex"); }
 657    }
 658    else {
 659  5033 sb.append("" + s.charAt(i));
 660    }
 661    }
 662  294 return sb.toString();
 663    }
 664   
 665    /** Convert a command line into a list of list of lists of individual
 666    * arguments. The outermost list is a list of list of lists of arguments
 667    * for processes separated by ProcessChain.PROCESS_SEPARATOR (either ';'
 668    * or ':', depending on which is NOT File.pathSeparatorChar).
 669    * The lists contained in the outermost list are lists of lists of
 670    * arguments for processes in the same piping chain, i.e. processes
 671    * separated by '|'.
 672    * The innermost lists are lists of arguments for the individual
 673    * processes.
 674    * This method keeps quoted parts together using ", ' and `.
 675    * It also keeps treats a '\u001b' followed by a space as non-breaking space.
 676    * And a double '\u001b' becomes a single '\u001b'.
 677    * It does not allow escaping of the quote characters. */
 678  36 public static List<List<List<String>>> commandLineToLists(String cmdline) {
 679  36 BalancingStreamTokenizer tok = new BalancingStreamTokenizer(new StringReader(cmdline));
 680  36 tok.wordRange(0,255);
 681  36 tok.addQuotes("${", "}");
 682  36 tok.addQuotes("\"", "\"");
 683  36 tok.addQuotes("'", "'");
 684  36 tok.addQuotes("`", "`");
 685  36 tok.addKeyword(ProcessChain.PROCESS_SEPARATOR);
 686  36 tok.addKeyword(ProcessChain.PIPE_SEPARATOR);
 687    // add whitespace characters as keyword, as per Character.isWhitespace
 688  36 tok.addKeyword(" ");
 689  36 tok.addKeyword(new Character((char)0x09).toString()); // horizontal tab
 690  36 tok.addKeyword(new Character((char)0x0A).toString()); // line feed
 691  36 tok.addKeyword(new Character((char)0x0B).toString()); // vertical tab
 692  36 tok.addKeyword(new Character((char)0x0C).toString()); // form feed / Character.SPACE_SEPARATOR
 693  36 tok.addKeyword(new Character((char)0x0D).toString()); // carriage return / Character.LINE_SEPARATOR
 694  36 tok.addKeyword(new Character((char)0x0E).toString()); // carriage return / Character.PARAGRAPH_SEPARATOR
 695  36 tok.addKeyword(new Character((char)0x1C).toString()); // file separator
 696  36 tok.addKeyword(new Character((char)0x1D).toString()); // group separator
 697  36 tok.addKeyword(new Character((char)0x1E).toString()); // record separator
 698  36 tok.addKeyword(new Character((char)0x1F).toString()); // unit separator
 699    // also add escaped space as keyword, but treat it differently
 700  36 final String ESCAPE = String.valueOf((char)0x1B);
 701  36 final String ESCAPED_SPACE = ESCAPE + " ";
 702  36 tok.addKeyword(ESCAPED_SPACE); // escaped space
 703    // also add escaped path separator (';' or ':') as keyword, but treat it differently
 704  36 final String ESCAPED_PATH_SEPARATOR = ESCAPE+java.io.File.pathSeparator;
 705  36 tok.addKeyword(ESCAPED_PATH_SEPARATOR); // escaped path separator
 706    // also add escaped process separator ('#') as keyword, but treat it differently
 707  36 final String ESCAPED_PROCESS_SEPARATOR = ESCAPE+ProcessChain.PROCESS_SEPARATOR;
 708  36 tok.addKeyword(ESCAPED_PROCESS_SEPARATOR); // escaped process separator
 709    // also add escaped pipe ('|') as keyword, but treat it differently
 710  36 final String ESCAPED_PIPE_SEPARATOR = ESCAPE+ProcessChain.PIPE_SEPARATOR;
 711  36 tok.addKeyword(ESCAPED_PIPE_SEPARATOR); // escaped pipe
 712    // also add escaped colon (':') as keyword on Windows, but treat it differently
 713  36 final String ESCAPED_COLON = ESCAPE + ":";
 714  36 if (!ESCAPED_COLON.equals(ESCAPED_PATH_SEPARATOR)) {
 715  0 tok.addKeyword(ESCAPED_COLON); // escaped colon
 716    }
 717    // also add escaped escape ('\u001b') as keyword, but treat it differently
 718  36 final String ESCAPED_ESCAPE = ESCAPE+ESCAPE;
 719  36 tok.addKeyword(ESCAPED_ESCAPE); // escaped escape
 720    // read tokens; concatenate tokens until keyword is found
 721  36 String n = null;
 722  36 StringBuilder sb = new StringBuilder();
 723  36 List<List<List<String>>> lll = new ArrayList<List<List<String>>>();
 724  36 List<List<String>> ll = new ArrayList<List<String>>();
 725  36 List<String> l = new ArrayList<String>();
 726  36 try {
 727  ? while((n=tok.getNextToken()) != null) {
 728  358 if (tok.token() == BalancingStreamTokenizer.Token.KEYWORD) {
 729  217 if (n.equals(ProcessChain.PROCESS_SEPARATOR)) {
 730    // add the current string to the argument list and start a new argument
 731  19 String arg = sb.toString();
 732  19 sb.setLength(0);
 733  2 if (arg.length() > 0) { l.add(arg); }
 734   
 735    // add the current list of arguments to the list of list and start a new
 736    // argument list
 737  19 ll.add(l);
 738  19 l = new ArrayList<String>();
 739   
 740    // add the current list of list to the outermost list and start a new
 741    // list of lists
 742  19 lll.add(ll);
 743  19 ll = new ArrayList<List<String>>();
 744    }
 745  198 else if (n.equals(ProcessChain.PIPE_SEPARATOR)) {
 746    // add the current string to the argument list and start a new argument
 747  30 String arg = sb.toString();
 748  30 sb.setLength(0);
 749  3 if (arg.length() > 0) { l.add(arg); }
 750   
 751    // add the current list of arguments to the list of list and start a new
 752    // argument list
 753  30 ll.add(l);
 754  30 l = new ArrayList<String>();
 755    }
 756  168 else if (n.equals(ESCAPED_SPACE) ||
 757    n.equals(ESCAPED_PATH_SEPARATOR) ||
 758    n.equals(ESCAPED_PROCESS_SEPARATOR) ||
 759    n.equals(ESCAPED_PIPE_SEPARATOR) ||
 760    n.equals(ESCAPED_COLON) ||
 761    n.equals(ESCAPED_ESCAPE)) {
 762    // escaped characters, append the string after the ESCAPE character
 763  40 sb.append(n.substring(ESCAPE.length()));
 764    }
 765    else { // must be whitespace
 766    // add the current string to the argument list and start a new argument
 767  128 String arg = sb.toString();
 768  128 sb.setLength(0);
 769  79 if (arg.length() > 0) { l.add(arg); }
 770    }
 771    }
 772    else {
 773  141 sb.append(n);
 774    }
 775    }
 776    }
 777    catch(IOException e) { /* ignore, return what we have */ }
 778   
 779    // add the current string to the argument list and start a new argument
 780  36 String arg = sb.toString();
 781  36 sb.setLength(0);
 782  36 if (arg.length() > 0) { l.add(arg); }
 783   
 784    // add the current list of arguments to the list of list and start a new
 785    // argument list
 786  36 ll.add(l);
 787  36 l = new ArrayList<String>();
 788   
 789    // add the current list of list to the outermost list and start a new
 790    // list of lists
 791  36 lll.add(ll);
 792  36 ll = new ArrayList<List<String>>();
 793   
 794  36 return lll;
 795    }
 796   
 797   
 798    /** Replace variables of the form "${variable}" with the value associated with the string "variable" in the
 799    * provided hash table.
 800    * To give the "$" character its literal meaning, it needs to be escaped as "\$" (backslash dollar).
 801    * To make the "\" character not escaping, escape it as "\\"(double backslash).
 802    * @param str input string
 803    * @param props map with maps of variable-value pairs
 804    * @param getter lambda from a DrJavaProperty to String
 805    * @return string with variables replaced by values
 806    */
 807  293 public static String replaceVariables(String str, final PropertyMaps props, final Lambda2<DrJavaProperty,PropertyMaps,String> getter) {
 808  293 BalancingStreamTokenizer tok = new BalancingStreamTokenizer(new StringReader(str), '$');
 809  293 tok.wordRange(0,255);
 810  293 tok.addQuotes("${", "}");
 811  293 tok.addQuotes("\"", "\"");
 812   
 813    // LOG.log("---------");
 814    // LOG.log("Replacing: " + str);
 815  293 StringBuilder sb = new StringBuilder();
 816  293 String next = null;
 817  293 try {
 818  ? while((next=tok.getNextToken()) != null) {
 819    // LOG.log("Token: " + next);
 820  278 if ((tok.token() == BalancingStreamTokenizer.Token.QUOTED) &&
 821    (next.startsWith("${")) &&
 822    (next.endsWith("}"))) {
 823    // LOG.log("Found property: " + next);
 824  157 String key;
 825  157 String attrList = "";
 826  157 int firstCurly = next.indexOf('}');
 827  157 int firstSemi = next.indexOf(';');
 828  157 if (firstSemi < 0) {
 829    // format: ${property.name}
 830    // for key, cut off ${ and }
 831  133 key = next.substring(2,firstCurly);
 832    }
 833    else {
 834    // format: {$property.name;...}
 835    // for key, cut off ${ and ;...}
 836  24 key = next.substring(2,firstSemi);
 837    // for attribute list, cut off ${propertyname; and }
 838  24 attrList = next.substring(firstSemi+1,next.length()-1).trim();
 839    }
 840    // LOG.log("\tKey = '" + key + "'");
 841    // LOG.log("\tAttrList = '" + attrList + "'");
 842  157 DrJavaProperty p = props.getProperty(key);
 843  157 if (p != null) {
 844    // found property name
 845  144 p.resetAttributes();
 846   
 847    // if we have a list of attributes
 848  144 try {
 849  144 if (attrList.length() > 0) {
 850  24 BalancingStreamTokenizer atok = new BalancingStreamTokenizer(new StringReader(attrList), '$');
 851  24 atok.wordRange(0,255);
 852  24 atok.whitespaceRange(0,32);
 853  24 atok.addQuotes("\"", "\"");
 854  24 atok.addQuotes("${", "}");
 855  24 atok.addKeyword(";");
 856  24 atok.addKeyword("=");
 857    // LOG.log("\tProcessing AttrList");
 858  24 String n = null;
 859  24 HashMap<String,String> attrs = new HashMap<String,String>();
 860  ? while((n=atok.getNextToken()) != null) {
 861  27 if ((n == null) || (atok.token() != BalancingStreamTokenizer.Token.NORMAL) ||
 862    n.equals(";") || n.equals("=") || n.startsWith("\"")) {
 863  3 throw new IllegalArgumentException("Unknown attribute list format for property " + key + "; expected name, but was " + n);
 864    }
 865  24 String name = n;
 866    // LOG.log("\t\tname = '" + name + "'");
 867  24 n = atok.getNextToken();
 868  24 if ((n == null) || (atok.token() != BalancingStreamTokenizer.Token.KEYWORD) || (!n.equals("="))) {
 869  5 throw new IllegalArgumentException("Unknown attribute list format for property " + key + "; expected =, but was " + n);
 870    }
 871    // LOG.log("\t\tread '='");
 872  19 n = atok.getNextToken();
 873  19 if ((n == null) || (atok.token() != BalancingStreamTokenizer.Token.QUOTED) || (!n.startsWith("\""))) {
 874  5 throw new IllegalArgumentException("Unknown attribute list format for property " + key + "; expected \", but was " + n);
 875    }
 876  14 String value = "";
 877  14 if (n.length()>1) {
 878  14 value = n.substring(1,n.length()-1);
 879    // LOG.log("\t\tvalue = '" + value + "'");
 880    }
 881  14 n = atok.getNextToken();
 882  14 if (((n != null) && ((atok.token() != BalancingStreamTokenizer.Token.KEYWORD) || (!n.equals(";")))) ||
 883    ((n == null) && (atok.token() != BalancingStreamTokenizer.Token.END))) {
 884  4 throw new IllegalArgumentException("Unknown attribute list format for property " + key);
 885    }
 886    // LOG.log("\t\tread ';' or EOF");
 887    // processed correctly
 888    // LOG.log("\t\treplacing variables in '" + value + "'...");
 889    // String replacedValue = replaceVariables(value, props, getter);
 890    // LOG.log("\t\treplaced value is '" + replacedValue + "'");
 891  10 attrs.put(name,value);
 892    // p.setAttribute(name, replacedValue);
 893   
 894  6 if (n == null) { break; }
 895    }
 896  7 p.setAttributes(attrs, new Lambda<String,String>() {
 897  10 public String value(String param) {
 898  10 return replaceVariables(param, props, getter);
 899    }
 900    });
 901    }
 902    // append the value of the property, e.g. /home/user instead of "${property.name}"
 903  125 String finalValue = getter.value(p,props);
 904    // LOG.log("\tfinal value: '" + finalValue + "'");
 905  123 sb.append(finalValue);
 906    }
 907    catch(IllegalArgumentException e) {
 908  18 sb.append("<-- Error: " + e.getMessage() + " -->");
 909    }
 910    }
 911    else {
 912    // unknown property
 913  13 sb.append(next);
 914    }
 915    }
 916    else {
 917  121 sb.append(next);
 918    }
 919    }
 920    }
 921    catch(IllegalArgumentException e) {
 922  0 return "<-- Error: " + e.getMessage() + " -->";
 923    }
 924    catch(IOException e) {
 925  0 return "<-- Error: " + e.getMessage() + " -->";
 926    }
 927   
 928    // LOG.log("Returning '" + sb.toString() + "'");
 929    // LOG.log("---------");
 930   
 931  290 return sb.toString();
 932    }
 933   
 934    /** Split a string into lines at a certain width, at word boundaries.
 935    * @param s string to split
 936    * @param widthInChars approximate width of the new lines
 937    * @param lineBreak string to be inserted at line breaks
 938    * @param wordSepChars string of characters that can serve as word separators
 939    */
 940  24 public static String splitStringAtWordBoundaries(String s, int widthInChars,
 941    String lineBreak,
 942    String wordSepChars) {
 943  24 StringBuilder sb = new StringBuilder();
 944    // remove word separators at the beginning of the string
 945  24 while(s.length() > 0) {
 946  24 if (wordSepChars.indexOf(String.valueOf(s.charAt(0))) >= 0) {
 947    // System.out.println("Removing leading separator...");
 948  0 s = s.substring(1);
 949    }
 950  24 else { break; /* first character that is not a separator */ }
 951    }
 952    // remove word separators at the end of the string
 953  24 while(s.length() > 0) {
 954  24 if (wordSepChars.indexOf(String.valueOf(s.charAt(s.length()-1))) >= 0) {
 955    // System.out.println("Removing trailing separator...");
 956  0 s = s.substring(0, s.length()-1);
 957    }
 958  24 else { break; /* first character that is not a separator */ }
 959    }
 960    // System.out.println("Removed all leading and trailing separator chars");
 961    // System.out.println("String: " + s);
 962   
 963  24 java.util.StringTokenizer tok = new java.util.StringTokenizer(s, wordSepChars);
 964  24 StringBuilder sbl = new StringBuilder(); // current line
 965    // System.out.println("hasMoreElements? " + tok.hasMoreElements());
 966  24 while(tok.hasMoreElements()) {
 967  240 String token = tok.nextToken();
 968    // System.out.println("\ttoken: " + token);
 969  240 sbl.append(token);
 970    // System.out.println("\tline (length=" + sbl.length() + "): " + sbl.toString());
 971  240 if (sbl.length() >= widthInChars) {
 972    // System.out.println("\tnewline");
 973    // System.out.println("\t\thasMoreElements? " + tok.hasMoreElements());
 974  30 if (tok.hasMoreElements()) {
 975    // System.out.println("\t\tinserting line break");
 976  18 sbl.append(lineBreak);
 977    }
 978    // System.out.println("\t\tFinal line: " + sbl.toString());
 979  30 sb.append(sbl.toString());
 980    // System.out.println("\t\tEntire buffer: " + sb.toString());
 981  30 sbl.setLength(0);
 982    }
 983  210 else { sbl.append(" "); }
 984    }
 985    // System.out.println("No more tokens. Last line: " + sbl.toString());
 986  12 if (sbl.length() > 0) { sb.append(sbl.toString()); }
 987   
 988    // System.out.println("Final entire buffer: " + sb.toString());
 989  24 return sb.toString();
 990    }
 991   
 992    /** Return a string containing a hexdump of the input string. The string will be formatted in the canonical hexdump format:
 993    * xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx | aaaaaaaaaaaaaaaa
 994    * @param s string to dump
 995    * @return hexdump string */
 996  0 public static String toStringHexDump(String s) {
 997  0 StringWriter sw = new StringWriter();
 998  0 PrintWriter pw = new PrintWriter(sw);
 999  0 StringBuilder sb = new StringBuilder();
 1000  0 for(int i = 0; i < s.length(); ++i) {
 1001  0 char ch = s.charAt(i);
 1002  0 pw.printf("%02x ",(int)ch);
 1003  0 if (ch<32) ch = ' ';
 1004  0 sb.append(ch);
 1005  0 if (i%16==7) {
 1006  0 pw.printf(" ");
 1007    }
 1008  0 else if (i%16==15) {
 1009  0 pw.printf("| %s\n", sb.toString());
 1010  0 sb.setLength(0);
 1011    }
 1012    }
 1013  0 if (s.length()%16 > 0) {
 1014  0 for(int i = 0; i < 16-(s.length()%16);++i) {
 1015  0 pw.printf(" ");
 1016  0 sb.append(' ');
 1017  0 if ((s.length()+i)%16==7) {
 1018  0 pw.printf(" ");
 1019  0 sb.append(' ');
 1020    }
 1021  0 else if ((s.length()+i)%16==15) {
 1022  0 pw.printf("| %s", sb.toString());
 1023  0 sb.setLength(0);
 1024    }
 1025    }
 1026    }
 1027  0 return sw.toString();
 1028    }
 1029   
 1030    /** Remove HTML tags from the string.
 1031    * Based on http://stackoverflow.com/questions/240546/removing-html-from-a-java-string
 1032    * @param s string with HTML tags
 1033    * @return string without HTML tags. */
 1034  0 public static String removeHTML(String s) {
 1035  0 try {
 1036  0 StringReader in = new StringReader(s);
 1037  0 final StringBuilder sb = new StringBuilder();
 1038  0 HTMLEditorKit.ParserCallback parser = new HTMLEditorKit.ParserCallback() {
 1039  0 public void handleText(char[] text, int pos) {
 1040  0 sb.append(text);
 1041    }
 1042    };
 1043  0 ParserDelegator delegator = new ParserDelegator();
 1044    // the third parameter is TRUE to ignore charset directive
 1045  0 delegator.parse(in, parser, Boolean.TRUE);
 1046  0 in.close();
 1047  0 return sb.toString();
 1048    }
 1049  0 catch(IOException ioe) { throw new UnexpectedException(ioe); }
 1050    }
 1051    }