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