Home | History | Annotate | Download | only in docs
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html#License
      3 /**
      4 *******************************************************************************
      5 * Copyright (C) 2004-2012, International Business Machines Corporation and    *
      6 * others. All Rights Reserved.                                                *
      7 *******************************************************************************
      8 */
      9 
     10 package com.ibm.icu.dev.tool.docs;
     11 
     12 import java.io.BufferedReader;
     13 import java.io.File;
     14 import java.io.FileInputStream;
     15 import java.io.FileOutputStream;
     16 import java.io.FilenameFilter;
     17 import java.io.IOException;
     18 import java.io.InputStream;
     19 import java.io.InputStreamReader;
     20 import java.io.PrintStream;
     21 import java.util.ArrayList;
     22 import java.util.HashMap;
     23 import java.util.Iterator;
     24 import java.util.Map;
     25 import java.util.TreeMap;
     26 
     27 
     28 /**
     29  * A simple facility for adding C-like preprocessing to .java files.
     30  * This only understands a subset of the C preprocessing syntax.
     31  * Its used to manage files that with only small differences can be
     32  * compiled for different JVMs.  This changes files in place,
     33  * commenting out lines based on the current flag settings.
     34  */
     35 public class CodeMangler {
     36     private File indir;        // root of input
     37     private File outdir;       // root of output
     38     private String suffix;     // suffix to process, default '.jpp'
     39     private boolean recurse;   // true if recurse on directories
     40     private boolean force;     // true if force reprocess of files
     41     private boolean clean;     // true if output is to be cleaned
     42     private boolean timestamp; // true if we read/write timestamp
     43     private boolean nonames;   // true if no names in header
     44     private HashMap map;       // defines
     45     private ArrayList names;   // files/directories to process
     46     private String header;     // sorted list of defines passed in
     47 
     48     private boolean verbose; // true if we emit debug output
     49 
     50     private static final String IGNORE_PREFIX = "//##";
     51     private static final String HEADER_PREFIX = "//##header";
     52 
     53     public static void main(String[] args) {
     54         new CodeMangler(args).run();
     55     }
     56 
     57     private static final String usage = "Usage:\n" +
     58         "    CodeMangler [flags] file... dir... @argfile... \n" +
     59         "-in[dir] path          - root directory of input files, otherwise use current directory\n" +
     60         "-out[dir] path         - root directory of output files, otherwise use input directory\n" +
     61         "-s[uffix] string       - suffix of inputfiles to process, otherwise use '.java' (directories only)\n" +
     62         "-c[lean]               - remove all control flags from code on output (does not proceed if overwriting)\n" +
     63         "-r[ecurse]             - if present, recursively process subdirectories\n" +
     64         "-f[orce]               - force reprocessing of files even if timestamp and headers match\n" +
     65         "-t[imestamp]           - expect/write timestamp in header\n" +
     66         "-dNAME[=VALUE]         - define NAME with optional value VALUE\n" +
     67         "  (or -d NAME[=VALUE])\n" +
     68         "-n                     - do not put NAME/VALUE in header\n" +
     69         "-help                  - print this usage message and exit.\n" +
     70         "\n" +
     71         "For file arguments, output '.java' files using the same path/name under the output directory.\n" +
     72         "For directory arguments, process all files with the defined suffix in the directory.\n" +
     73         "  (if recursing, do the same for all files recursively under each directory)\n" +
     74         "For @argfile arguments, read the specified text file (strip the '@'), and process each line of that file as \n" +
     75         "an argument.\n" +
     76         "\n" +
     77         "Directives are one of the following:\n" +
     78         "  #ifdef, #ifndef, #else, #endif, #if, #elif, #define, #undef\n" +
     79         "These may optionally be preceeded by whitespace or //.\n" +
     80         "#if, #elif args are of the form 'key == value' or 'key != value'.\n" +
     81         "Only exact character match key with value is performed.\n" +
     82         "#define args are 'key [==] value', the '==' is optional.\n";
     83 
     84     CodeMangler(String[] args) {
     85         map = new HashMap();
     86         names = new ArrayList();
     87         suffix = ".java";
     88         clean = false;
     89         timestamp = false;
     90 
     91         String inname = null;
     92         String outname = null;
     93         boolean processArgs = true;
     94         String arg = null;
     95         try {
     96             for (int i = 0; i < args.length; ++i) {
     97                 arg = args[i];
     98                 if ("--".equals(arg)) {
     99                     processArgs = false;
    100                 } else if (processArgs && arg.charAt(0) == '-') {
    101                     if (arg.startsWith("-in")) {
    102                         inname = args[++i];
    103                     } else if (arg.startsWith("-out")) {
    104                         outname = args[++i];
    105                     } else if (arg.startsWith("-d")) {
    106                         String id = arg.substring(2);
    107                         if (id.length() == 0) {
    108                             id = args[++i];
    109                         }
    110                         String val = "";
    111                         int ix = id.indexOf('=');
    112                         if (ix >= 0) {
    113                             val = id.substring(ix+1);
    114                             id = id.substring(0,ix);
    115                         }
    116                         map.put(id, val);
    117                     } else if (arg.startsWith("-s")) {
    118                         suffix = args[++i];
    119                     } else if (arg.startsWith("-r")) {
    120                         recurse = true;
    121                     } else if (arg.startsWith("-f")) {
    122                         force = true;
    123                     } else if (arg.startsWith("-c")) {
    124                         clean = true;
    125                     } else if (arg.startsWith("-t")) {
    126                         timestamp = true;
    127                     } else if (arg.startsWith("-h")) {
    128                         System.out.print(usage);
    129                         break; // stop before processing arguments, so we will do nothing
    130                     } else if (arg.startsWith("-v")) {
    131                         verbose = true;
    132                     } else if (arg.startsWith("-n")) {
    133                         nonames = true;
    134                     } else {
    135                         System.err.println("Error: unrecognized argument '" + arg + "'");
    136                         System.err.println(usage);
    137                         throw new IllegalArgumentException(arg);
    138                     }
    139                 } else {
    140                     if (arg.charAt(0) == '@') {
    141                         File argfile = new File(arg.substring(1));
    142                         if (argfile.exists() && !argfile.isDirectory()) {
    143                             BufferedReader br = null;
    144                             try {
    145                                 br = new BufferedReader(new InputStreamReader(new FileInputStream(argfile)));
    146                                 ArrayList list = new ArrayList();
    147                                 for (int x = 0; x < args.length; ++x) {
    148                                     list.add(args[x]);
    149                                 }
    150                                 String line;
    151                                 while (null != (line = br.readLine())) {
    152                                     line = line.trim();
    153                                     if (line.length() > 0 && line.charAt(0) != '#') {
    154                                         if (verbose) System.out.println("adding argument: " + line);
    155                                         list.add(line);
    156                                     }
    157                                 }
    158                                 args = (String[])list.toArray(new String[list.size()]);
    159                             }
    160                             catch (IOException e) {
    161                                 System.err.println("error reading arg file: " + e);
    162                             }
    163                             finally {
    164                                 if (br != null) {
    165                                     try {
    166                                         br.close();
    167                                     } catch (Exception e){
    168                                         // ignore
    169                                     }
    170                                 }
    171                             }
    172                         }
    173                     } else {
    174                         names.add(arg);
    175                     }
    176                 }
    177             }
    178         } catch (IndexOutOfBoundsException e) {
    179             String msg = "Error: argument '" + arg + "' missing value";
    180             System.err.println(msg);
    181             System.err.println(usage);
    182             throw new IllegalArgumentException(msg);
    183         }
    184 
    185         String username = System.getProperty("user.dir");
    186         if (inname == null) {
    187             inname = username;
    188         } else if (!(inname.startsWith("\\") || inname.startsWith("/"))) {
    189             inname = username + File.separator + inname;
    190         }
    191         indir = new File(inname);
    192         try {
    193             indir = indir.getCanonicalFile();
    194         }
    195         catch (IOException e) {
    196             // continue, but most likely we'll fail later
    197         }
    198         if (!indir.exists()) {
    199             throw new IllegalArgumentException("Input directory '" + indir.getAbsolutePath() + "' does not exist.");
    200         } else if (!indir.isDirectory()) {
    201             throw new IllegalArgumentException("Input path '" + indir.getAbsolutePath() + "' is not a directory.");
    202         }
    203         if (verbose) System.out.println("indir: " + indir.getAbsolutePath());
    204 
    205         if (outname == null) {
    206             outname = inname;
    207         } else if (!(outname.startsWith("\\") || outname.startsWith("/"))) {
    208             outname = username + File.separator + outname;
    209         }
    210         outdir = new File(outname);
    211         try {
    212             outdir = outdir.getCanonicalFile();
    213         }
    214         catch (IOException e) {
    215             // continue, but most likely we'll fail later
    216         }
    217         if (!outdir.exists()) {
    218             throw new IllegalArgumentException("Output directory '" + outdir.getAbsolutePath() + "' does not exist.");
    219         } else if (!outdir.isDirectory()) {
    220             throw new IllegalArgumentException("Output path '" + outdir.getAbsolutePath() + "' is not a directory.");
    221         }
    222         if (verbose) System.out.println("outdir: " + outdir.getAbsolutePath());
    223 
    224         if (clean && suffix.equals(".java")) {
    225             try {
    226                 if (outdir.getCanonicalPath().equals(indir.getCanonicalPath())) {
    227                     throw new IllegalArgumentException("Cannot use 'clean' to overwrite .java files in same directory tree");
    228                 }
    229             }
    230             catch (IOException e) {
    231                 System.err.println("possible overwrite, error: " + e.getMessage());
    232                 throw new IllegalArgumentException("Cannot use 'clean' to overrwrite .java files");
    233             }
    234         }
    235 
    236         if (names.isEmpty()) {
    237             names.add(".");
    238         }
    239 
    240         TreeMap sort = new TreeMap(String.CASE_INSENSITIVE_ORDER);
    241         sort.putAll(map);
    242         Iterator iter = sort.entrySet().iterator();
    243         StringBuffer buf = new StringBuffer();
    244         if (!nonames) {
    245             while (iter.hasNext()) {
    246                 Map.Entry e = (Map.Entry)iter.next();
    247                 if (buf.length() > 0) {
    248                     buf.append(", ");
    249                 }
    250                 buf.append(e.getKey());
    251                 String v = (String)e.getValue();
    252                 if (v != null && v.length() > 0) {
    253                     buf.append('=');
    254                     buf.append(v);
    255                 }
    256             }
    257         }
    258         header = buf.toString();
    259     }
    260 
    261     public int run() {
    262         return process("", (String[])names.toArray(new String[names.size()]));
    263     }
    264 
    265     public int process(String path, String[] filenames) {
    266         if (verbose) System.out.println("path: '" + path + "'");
    267         int count = 0;
    268         for (int i = 0; i < filenames.length; ++i) {
    269             if (verbose) System.out.println("name " + i + " of " + filenames.length + ": '" + filenames[i] + "'");
    270             String name = path + filenames[i];
    271             File fin = new File(indir, name);
    272             try {
    273                 fin = fin.getCanonicalFile();
    274             }
    275             catch (IOException e) {
    276             }
    277             if (!fin.exists()) {
    278                 System.err.println("File " + fin.getAbsolutePath() + " does not exist.");
    279                 continue;
    280             }
    281             if (fin.isFile()) {
    282                 if (verbose) System.out.println("processing file: '" + fin.getAbsolutePath() + "'");
    283                 String oname;
    284                 int ix = name.lastIndexOf(".");
    285                 if (ix != -1) {
    286                     oname = name.substring(0, ix);
    287                 } else {
    288                     oname = name;
    289                 }
    290                 oname += ".java";
    291                 File fout = new File(outdir, oname);
    292                 if (processFile(fin, fout)) {
    293                     ++count;
    294                 }
    295             } else if (fin.isDirectory()) {
    296                 if (verbose) System.out.println("recursing on directory '" + fin.getAbsolutePath() + "'");
    297                 String npath = ".".equals(name) ? path : path + fin.getName() + File.separator;
    298                 count += process(npath, fin.list(filter)); // recursive call
    299             }
    300         }
    301         return count;
    302     }
    303 
    304 
    305     private final FilenameFilter filter = new FilenameFilter() {
    306             public boolean accept(File dir, String name) {
    307                 File f = new File(dir, name);
    308                 return (f.isFile() && name.endsWith(suffix)) || (f.isDirectory() && recurse);
    309             }
    310         };
    311 
    312     public boolean processFile(File infile, File outfile) {
    313         File backup = null;
    314 
    315         class State {
    316             int lc;
    317             String line;
    318             boolean emit = true;
    319             boolean tripped;
    320             private State next;
    321 
    322             public String toString() {
    323                 return "line " + lc
    324                     + ": '" + line
    325                     + "' (emit: " + emit
    326                     + " tripped: " + tripped
    327                     + ")";
    328             }
    329 
    330             void trip(boolean trip) {
    331                 if (!tripped & trip) {
    332                     tripped = true;
    333                     emit = next != null ? next.emit : true;
    334                 } else {
    335                     emit = false;
    336                 }
    337             }
    338 
    339             State push(int lc, String line, boolean trip) {
    340                 this.lc = lc;
    341                 this.line = line;
    342                 State ret = new State();
    343                 ret.next = this;
    344                 ret.emit = this.emit & trip;
    345                 ret.tripped = trip;
    346                 return ret;
    347             }
    348 
    349             State pop() {
    350                 return next;
    351             }
    352         }
    353 
    354         HashMap oldMap = null;
    355 
    356         long outModTime = 0;
    357 
    358         try {
    359             PrintStream outstream = null;
    360             InputStream instream = new FileInputStream(infile);
    361 
    362             BufferedReader reader = new BufferedReader(new InputStreamReader(instream));
    363             int lc = 0;
    364             State state = new State();
    365             String line;
    366             while ((line = reader.readLine()) != null) {
    367                 if (lc == 0) { // check and write header for output file if needed
    368                     boolean hasHeader = line.startsWith(HEADER_PREFIX);
    369                     if (hasHeader && !force) {
    370                         long expectLastModified = ((infile.lastModified() + 999)/1000)*1000;
    371                         String headerline = HEADER_PREFIX;
    372                         if (header.length() > 0) {
    373                             headerline += " ";
    374                             headerline += header;
    375                         }
    376                         if (timestamp) {
    377                             headerline += " ";
    378                             headerline += String.valueOf(expectLastModified);
    379                         }
    380                         if (line.equals(headerline)) {
    381                             if (verbose) System.out.println("no changes necessary to " + infile.getCanonicalPath());
    382                             reader.close();
    383                             return false; // nothing to do
    384                         }
    385                         if (verbose) {
    386                             System.out.println("  old header:  " + line);
    387                             System.out.println("  != expected: " + headerline);
    388                         }
    389                     }
    390 
    391                     // create output file directory structure
    392                     String outpname = outfile.getParent();
    393                     if (outpname != null) {
    394                         File outp = new File(outpname);
    395                         if (!(outp.exists() || outp.mkdirs())) {
    396                             System.err.println("could not create directory: '" + outpname + "'");
    397                             reader.close();
    398                             return false;
    399                         }
    400                     }
    401 
    402                     // if we're overwriting, use a temporary file
    403                     if (suffix.equals(".java")) {
    404                         backup = outfile;
    405                         try {
    406                             outfile = File.createTempFile(outfile.getName(), null, outfile.getParentFile());
    407                         }
    408                         catch (IOException ex) {
    409                             System.err.println(ex.getMessage());
    410                             reader.close();
    411                             return false;
    412                         }
    413                     }
    414 
    415                     outModTime = ((outfile.lastModified()+999)/1000)*1000; // round up
    416                     outstream = new PrintStream(new FileOutputStream(outfile));
    417                     String headerline = HEADER_PREFIX;
    418                     if (header.length() > 0) {
    419                         headerline += " ";
    420                         headerline += header;
    421                     }
    422                     if (timestamp) {
    423                         headerline += " ";
    424                         headerline += String.valueOf(outModTime);
    425                     }
    426                     outstream.println(headerline);
    427                     if (verbose) System.out.println("header: " + headerline);
    428 
    429                     // discard the old header if we had one, otherwise match this line like any other
    430                     if (hasHeader) {
    431                         ++lc; // mark as having read a line so we never reexecute this block
    432                         continue;
    433                     }
    434                 }
    435 
    436                 String[] res = new String[3];
    437                 if (patMatch(line, res)) {
    438                     String lead = res[0];
    439                     String key = res[1];
    440                     String val = res[2];
    441 
    442                     if (verbose) System.out.println("directive: " + line
    443                                                     + " key: '" + key
    444                                                     + "' val: '" + val
    445                                                     + "' " + state);
    446                     if (key.equals("ifdef")) {
    447                         state = state.push(lc, line, map.get(val) != null);
    448                     } else if (key.equals("ifndef")) {
    449                         state = state.push(lc, line, map.get(val) == null);
    450                     } else if (key.equals("else")) {
    451                         state.trip(true);
    452                     } else if (key.equals("endif")) {
    453                         state = state.pop();
    454                     } else if (key.equals("undef")) {
    455                         if (state.emit) {
    456                             if (oldMap == null) {
    457                                 oldMap = (HashMap)map.clone();
    458                             }
    459                             map.remove(val);
    460                         }
    461                     } else if (key.equals("define")) {
    462                         if (pat2Match(val, res)) {
    463                             String key2 = res[0];
    464                             String val2 = res[2];
    465 
    466                             if (verbose) System.out.println("val2: '" + val2
    467                                                             + "' key2: '" + key2
    468                                                             + "'");
    469                             if (state.emit) {
    470                                 if (oldMap == null) {
    471                                     oldMap = (HashMap)map.clone();
    472                                 }
    473                                 map.put(key2, val2);
    474                             }
    475                         }
    476                     } else { // #if, #elif
    477                         // only top level OR (||) operator is supported for now
    478                         int count = 1;
    479                         int index = 0;
    480                         while ((index = val.indexOf("||", index)) > 0) {
    481                             count++;
    482                             index++;
    483                         }
    484                         String[] expressions = new String[count];
    485                         if (count == 1) {
    486                             expressions[0] = val;
    487                         } else {
    488                             int start = 0;
    489                             index = 0;
    490                             count = 0;
    491                             while (true) {
    492                                 index = val.indexOf("||", start);
    493                                 if (index > 0) {
    494                                     expressions[count++] = val.substring(start, index);
    495                                     start = index + 2;
    496                                 } else {
    497                                     expressions[count++] = val.substring(start);
    498                                     break;
    499                                 }
    500                             }
    501                         }
    502                         boolean eval = false;
    503                         for (count = 0; count < expressions.length && !eval; count++) {
    504                             if (pat2Match(expressions[count], res)) {
    505                                 String key2 = res[0];
    506                                 String val2 = res[2];
    507 
    508                                 if (key2.equals("defined")) {
    509                                     // defined command
    510                                     if (verbose) System.out.println(
    511                                             "index: '" + count
    512                                             + "' val2: '" + val2
    513                                             + "' key2: '" + key2
    514                                             + "'");
    515                                     eval = map.containsKey(val2);
    516                                 } else {
    517                                     boolean neq = false;
    518                                     if (res[1].equals("!=")) {
    519                                         neq = true;
    520                                     } else if (!res[1].equals("==")) {
    521                                         System.err.println("Invalid expression: '" + val);
    522                                     }
    523                                     if (verbose) System.out.println(
    524                                             "index: '" + count
    525                                             + "' val2: '" + val2
    526                                             + "' neq: '" + neq
    527                                             + "' key2: '" + key2
    528                                             + "'");
    529                                     eval = (val2.equals(map.get(key2)) != neq);
    530                                 }
    531                             }
    532                         }
    533                         if (key.equals("if")) {
    534                             state = state.push(lc, line, eval);
    535                         } else if (key.equals("elif")) {
    536                             state.trip(eval);
    537                         }
    538                     }
    539                     if (!clean) {
    540                         lc++;
    541                         if (!lead.equals("//")) {
    542                             outstream.print("//");
    543                             line = line.substring(lead.length());
    544                         }
    545                         outstream.println(line);
    546                     }
    547                     continue;
    548                 }
    549 
    550                 lc++;
    551                 String found = pat3Match(line);
    552                 boolean hasIgnore = found != null;
    553                 if (state.emit == hasIgnore) {
    554                     if (state.emit) {
    555                         line = line.substring(found.length());
    556                     } else {
    557                         line = IGNORE_PREFIX + line;
    558                     }
    559                 } else if (hasIgnore && !found.equals(IGNORE_PREFIX)) {
    560                     line = IGNORE_PREFIX + line.substring(found.length());
    561                 }
    562                 if (!clean || state.emit) {
    563                     outstream.println(line);
    564                 }
    565             }
    566 
    567             state = state.pop();
    568             if (state != null) {
    569                 System.err.println("Error: unclosed directive(s):");
    570                 do {
    571                     System.err.println(state);
    572                 } while ((state = state.pop()) != null);
    573                 System.err.println(" in file: " + outfile.getCanonicalPath());
    574                 if (oldMap != null) {
    575                     map = oldMap;
    576                 }
    577                 reader.close();
    578                 outstream.close();
    579                 return false;
    580             }
    581 
    582             outstream.close();
    583             instream.close();
    584 
    585             if (backup != null) {
    586                 if (backup.exists()) {
    587                     backup.delete();
    588                 }
    589                 outfile.renameTo(backup);
    590             }
    591 
    592             if (timestamp) {
    593                 outfile.setLastModified(outModTime); // synch with timestamp
    594             }
    595 
    596             if (oldMap != null) {
    597                 map = oldMap;
    598             }
    599         }
    600         catch (IOException e) {
    601             System.err.println(e);
    602             return false;
    603         }
    604         return true;
    605     }
    606 
    607 
    608     /**
    609      * Perform same operation as matching on pat.  on exit
    610      * leadKeyValue contains the three strings lead, key, and value.
    611      * 'lead' is the portion before the #ifdef directive.  'key' is
    612      * the directive.  'value' is the portion after the directive.  if
    613      * there is a match, return true, else return false.
    614      */
    615     static boolean patMatch(String line, String[] leadKeyValue) {
    616         if (line.length() == 0) {
    617             return false;
    618         }
    619         if (!line.endsWith("\n")) {
    620             line = line + '\n';
    621         }
    622         int mark = 0;
    623         int state = 0;
    624         loop: for (int i = 0; i < line.length(); ++i) {
    625             char c = line.charAt(i);
    626             switch (state) {
    627             case 0: // at start of line, haven't seen anything but whitespace yet
    628                 if (c == ' ' || c == '\t' || c == '\r') continue;
    629                 if (c == '/') { state = 1; continue; }
    630                 if (c == '#') { state = 4; continue; }
    631                 return false;
    632             case 1: // have seen a single slash after start of line
    633                 if (c == '/') { state = 2; continue; }
    634                 return false;
    635             case 2: // have seen two or more slashes
    636                 if (c == '/') continue;
    637                 if (c == ' ' || c == '\t' || c == '\r') { state = 3; continue; }
    638                 if (c == '#') { state = 4; continue; }
    639                 return false;
    640             case 3: // have seen a space after two or more slashes
    641                 if (c == ' ' || c == '\t' || c == '\r') continue;
    642                 if (c == '#') { state = 4; continue; }
    643                 return false;
    644             case 4: // have seen a '#'
    645                 leadKeyValue[0] = line.substring(mark, i-1);
    646                 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { mark = i; state = 5; continue; }
    647                 return false;
    648             case 5: // an ascii char followed the '#'
    649                 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) continue;
    650                 if (c == ' ' || c == '\t' || c == '\n') {
    651                     String key = line.substring(mark, i).toLowerCase();
    652                     if (key.equals("ifdef") ||
    653                         key.equals("ifndef") ||
    654                         key.equals("else") ||
    655                         key.equals("endif") ||
    656                         key.equals("undef") ||
    657                         key.equals("define") ||
    658                         key.equals("if") ||
    659                         key.equals("elif")) {
    660                         leadKeyValue[1] = key;
    661                         mark = i;
    662                         state = 6;
    663                         break loop;
    664                     }
    665                 }
    666                 return false;
    667             default:
    668                 throw new IllegalStateException();
    669             }
    670         }
    671         if (state == 6) {
    672             leadKeyValue[2] = line.substring(mark, line.length()).trim();
    673             return true;
    674         }
    675         return false; // never reached, does the compiler know this?
    676     }
    677 
    678     /**
    679      * Perform same operation as matching on pat2.  on exit
    680      * keyRelValue contains the three strings key, rel, and value.
    681      * 'key' is the portion before the relation (or final word).  'rel' is
    682      * the relation, if present, either == or !=.  'value' is the final
    683      * word.  if there is a match, return true, else return false.
    684      */
    685     static boolean pat2Match(String line, String[] keyRelVal) {
    686 
    687         if (line.length() == 0) {
    688             return false;
    689         }
    690         keyRelVal[0] = keyRelVal[1] = keyRelVal[2] = "";
    691         int mark = 0;
    692         int state = 0;
    693         String command = null;
    694         loop: for (int i = 0; i < line.length(); ++i) {
    695             char c = line.charAt(i);
    696             switch (state) {
    697             case 0: // saw beginning or space, no rel yet
    698                 if (c == ' ' || c == '\t' || c == '\n') {
    699                     continue;
    700                 }
    701                 if ((c == '!' || c == '=')) {
    702                     return false;
    703                 }
    704                 state = 1;
    705                 continue;
    706             case 1: // saw start of a word
    707                 if (c == ' ' || c == '\t') {
    708                     state = 2;
    709                 }
    710                 else if (c == '(') {
    711                     command = line.substring(0, i).trim();
    712                     if (!command.equals("defined")) {
    713                         return false;
    714                     }
    715                     keyRelVal[0] = command;
    716                     state = 2;
    717                 }
    718                 else if (c == '!' || c == '=') {
    719                     state = 3;
    720                 }
    721                 continue;
    722             case 2: // saw end of word, and space
    723                 if (c == ' ' || c == '\t') {
    724                     continue;
    725                 }
    726                 else if (command == null && c == '(') {
    727                     continue;
    728                 }
    729                 else if (c == '!' || c == '=') {
    730                     state = 3;
    731                     continue;
    732                 }
    733                 keyRelVal[0] = line.substring(0, i-1).trim();
    734                 mark = i;
    735                 state = 4;
    736                 break loop;
    737             case 3: // saw end of word, and '!' or '='
    738                 if (c == '=') {
    739                     keyRelVal[0] = line.substring(0, i-1).trim();
    740                     keyRelVal[1] = line.substring(i-1, i+1);
    741                     mark = i+1;
    742                     state = 4;
    743                     break loop;
    744                 }
    745                 return false;
    746             default:
    747                 break;
    748             }
    749         }
    750         switch (state) {
    751         case 0:
    752             return false; // found nothing
    753         case 1:
    754         case 2:
    755             keyRelVal[0] = line.trim(); break; // found only a word
    756         case 3:
    757             return false; // found a word and '!' or '=" then end of line, incomplete
    758         case 4:
    759             keyRelVal[2] = line.substring(mark).trim(); // found a word, possible rel, and who knows what
    760             if (command != null) {
    761                 int len = keyRelVal[2].length();
    762                 if (keyRelVal[2].charAt(len - 1) != ')') {
    763                     // closing parenthesis is missing
    764                     return false;
    765                 }
    766                 keyRelVal[2] = keyRelVal[2].substring(0, len - 1).trim();
    767             }
    768             break;
    769         default:
    770             throw new IllegalStateException();
    771         }
    772         return true;
    773     }
    774 
    775     static String pat3Match(String line) {
    776         int state = 0;
    777         loop: for (int i = 0; i < line.length(); ++i) {
    778             char c = line.charAt(i);
    779             switch(state) {
    780             case 0: if (c == ' ' || c == '\t') continue;
    781                 if (c == '/') { state = 1; continue; }
    782                 break loop;
    783             case 1:
    784                 if (c == '/') { state = 2; continue; }
    785                 break loop;
    786             case 2:
    787                 if (c == '#') { state = 3; continue; }
    788                 break loop;
    789             case 3:
    790                 if (c == '#') return line.substring(0, i+1);
    791                 break loop;
    792             default:
    793                 break loop;
    794             }
    795         }
    796         return null;
    797     }
    798 }
    799