View Javadoc

1   /*$Id: BnfExtractor.java,v 1.2 2008/09/08 15:37:23 jl99 Exp $
2    * Copyright (C) AstroGrid. All rights reserved.
3    *
4    * This software is published under the terms of the AstroGrid 
5    * Software License version 1.2, a copy of which has been included 
6    * with this distribution in the LICENSE.txt file.  
7    *
8   **/
9   package org.astrogrid.adql;
10  
11  import org.apache.commons.logging.Log ;
12  import org.apache.commons.logging.LogFactory ;
13  import java.io.* ;
14  import java.util.*;
15  import java.text.MessageFormat;
16  /**
17   * BnfExtractor
18   * <p/>
19   * Extracts bnf statements from an annotated javacc file.
20   * <p/>
21   * <blockquote><pre>
22   * Usage: BnfExtractor {Parameters}      
23   *   Parameters:
24   *    -input=file-path
25   *    -output=file-path
26   *    -text=text-header-path
27   *    -html=html-header-path
28   *    -conditional=condition-tag
29   *   Notes:
30   *    (1) All parameter triggers can be shortened to the first letter; ie: -i,-o,-t,-h,-c.
31   *    (2) The input parameter is the only mandatory one.
32   *    (3) If the output file-path parameter is omitted, output is directed to standard out.
33   *    (4) The text and html parameters are mutually exclusive: either text or html is produced.
34   *        The path given must be to a file containing suitable header data for the BNF diagrams.
35   *    (5) The conditional parameter is for selecting between various possible sets of BNF defined
36   *        within the base parser's AdqlStoX.jjt file. At present, only two condition tags are
37   *        available: v20+RFC and v20-AG, where v20+RFC will produce BNF at the v2.0 plus changes
38   *        associated with the RFC stage, and v20-AG will produce BNF at the projected level for
39   *        AstroGrid's first support of v2.0. So, a conditional parameter could be:
40   *        -c=v20+RFC
41   *        If no conditional parameter is present, v20+RFC is assumed.
42   *    (6) Ouput files must not already exist. If you wish to overwrite,
43   *        omit the output file parameter and pipe standard out.
44   * </pre></blockquote> 
45   * <p/>
46   * @author Jeff Lusted jl99@star.le.ac.uk
47   * Oct 22, 2006
48   */
49  public class BnfExtractor {
50      
51  private static Log log = LogFactory.getLog( BnfExtractor.class ) ;
52      
53      private static StringBuffer logIndent = new StringBuffer() ;
54      
55      private static final String USAGE =
56          "Usage: BnfExtractor {Parameters}\n" +       
57          "Parameters:\n" +
58          " -input=file-path\n" +
59          " -output=file-path\n" +
60          " -text=text-header-path\n" +
61          " -html=html-header-path\n" +
62          " -conditional=condition-tag\n" +
63          "Notes:\n" +
64          " (1) All parameter triggers can be shortened to the first letter; ie: -i,-o,-t,-h,-c.\n" +
65          " (2) The input parameter is the only mandatory one.\n" +
66          " (3) If the output file-path parameter is omitted, output is directed to standard out.\n" +
67          " (4) The text and html parameters are mutually exclusive: either text or html is produced.\n" +
68          "     The path given must be to a file containing suitable header data for the BNF diagrams.\n" +
69          " (5) The conditional parameter is for selecting between various possible sets of BNF defined\n" +
70          "     within the base parser's AdqlStoX.jjt file. At present, only two condition tags are\n" +
71          "     available: v20+RFC and v20-AG, where v20+RFC will produce BNF at the v2.0 plus changes\n" +
72          "     associated with the RFC stage, and v20-AG will produce BNF at the projected level for\n" +
73          "     AstroGrid's first support of v2.0. So, a conditional parameter could be:\n" +
74          "     -c=v20+RFC\n" +
75          "     If no conditional parameter is present, v20+RFC is assumed.\n" +
76          " (6) Ouput files must not already exist. If you wish to overwrite,\n" +
77          "     omit the output file parameter and pipe standard out." ;
78       
79      private static final String TEXT_FOOTINGS =
80          "" ;
81      
82      private static final String HTML_FOOTINGS =
83          "\n" +
84          "</pre>" +
85          "</body>" +  
86          "</html>" ;
87      
88      private static final String HTML_KEY_TEMPLATE = 
89          "<a name=\"{0}\">&lt;{0}&gt;</a>" ;
90      
91      private static final String HTML_DETAIL_TEMPLATE = 
92          "&lt;<a href=\"#{0}\">{0}</a>&gt;" ;
93  
94      private static final String BNF_SINGLE = " * bnf-single" ;
95      private static final String BNF_START = " * bnf-start" ;
96      private static final String BNF_END = " * bnf-end" ;
97      private static final String BNF_TRIGGER = "bnf-" ;
98      private static final String V20_PLUS_RFC = "v20+RFC" ;
99      
100     private static String formatOption = null ;
101     private static String inFilePath = null ;
102     private static String outFilePath = null ;
103     private static String headerFilePath = null ;
104     private static boolean overwrite = false ;
105     private static String[] aConditionals = null ;
106     
107     private String iFormatOption = null ;
108     private boolean iOverWriteOption = false ;
109     private File inputFile ;
110     private File outputFile ;  
111     private File headerFile ;
112     private FileReader reader = null ;
113     private FileReader headerReader = null ;
114     private MessageFormat keyFormat = new MessageFormat( HTML_KEY_TEMPLATE )  ;
115     private MessageFormat detailFormat = new MessageFormat( HTML_DETAIL_TEMPLATE )  ;
116     private OutputStream outputStream = null ;
117     
118     private StringBuffer lineBuffer = new StringBuffer( 100 ) ;
119     private boolean inputEOF = false ;
120     private ArrayList list = new ArrayList( 256 ) ;
121     private BnfStatementFactory statementFactory = new BnfStatementFactory() ;
122 
123     /**
124      * @param args
125      */
126     public static void main( String[] args ) {
127         if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor.main" ) ;
128         
129         if( retrieveArgs( args ) == false ) {
130             System.out.println() ;
131             System.out.print( USAGE ) ;
132             return ;
133         }
134         
135         File inputFile = new File( inFilePath ) ;
136         File outputFile = null ;
137         File headerFile = null ;
138         
139         if( inputFile.exists() == false ) {
140             System.out.println( "Input file does not exist:\n" +
141                                 "    " + inFilePath
142                               ) ;
143             return ;
144         }
145           
146         if( outFilePath != null ) {
147             outputFile = new File( outFilePath ) ;
148             if( outputFile.exists() == true ) {
149                 System.out.println( "Output file already exists:\n" +
150                                     "    " + outFilePath
151                                   ) ;
152                 return ;
153             }
154         }
155         
156         if( headerFilePath != null ) {
157             headerFile = new File( headerFilePath ) ;
158             if( headerFile.exists() == false ) {
159                 System.out.println( "Header file does not exist:\n"
160                                   + "    " + headerFilePath ) ;
161                 return ;
162             }
163         }
164         
165         if( BnfExtractor.formatOption == null ) {
166             BnfExtractor.formatOption = "t" ;
167         }
168                
169         BnfExtractor extractor = new BnfExtractor( inputFile, outputFile, formatOption, headerFile ) ;
170         
171         extractor.exec() ;
172         System.out.println( "BNF extraction complete." ) ;
173         if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor.main" ) ;
174     }
175     
176     private static boolean retrieveArgs( String[] args ) {
177         boolean retVal = false ;
178         if( args != null && args.length > 0 ) {
179             
180             for( int i=0; i<args.length; i++ ) {
181                 
182                 if( args[i].startsWith( "-text" ) ) {
183                     BnfExtractor.formatOption = "t" ;
184                     if( args[i].startsWith( "-text=") ) {
185                         BnfExtractor.headerFilePath = args[i].substring(6) ;
186                     }
187                 }
188                 else if( args[i].startsWith( "-t" ) ) {
189                     BnfExtractor.formatOption = "t" ;
190                     if( args[i].startsWith( "-t=") ) {
191                         BnfExtractor.headerFilePath = args[i].substring(3) ;
192                     }
193                 }
194                 else if( args[i].startsWith( "-html" ) ) {
195                     BnfExtractor.formatOption = "h" ; 
196                     if( args[i].startsWith( "-html=") ) {
197                         BnfExtractor.headerFilePath = args[i].substring(6) ;
198                     }
199                 }
200                 else if( args[i].startsWith( "-h" ) ) {
201                     BnfExtractor.formatOption = "h" ; 
202                     if( args[i].startsWith( "-h=") ) {
203                         BnfExtractor.headerFilePath = args[i].substring(3) ;
204                     }
205                 }               
206                 else if( args[i].startsWith( "-input=" ) ) { 
207                     BnfExtractor.inFilePath = args[i].substring(7) ;
208                 }
209                 else if( args[i].startsWith( "-i=" ) ) { 
210                     BnfExtractor.inFilePath = args[i].substring(2) ;
211                 }
212                 else if( args[i].startsWith( "-output=" ) ) { 
213                     BnfExtractor.outFilePath = args[i].substring(8) ;
214                 }
215                 else if( args[i].startsWith( "-o=" ) ) { 
216                     BnfExtractor.outFilePath = args[i].substring(3) ;
217                     if( log.isDebugEnabled() ){
218                         log.debug( "o=" + BnfExtractor.outFilePath ) ;
219                     }
220                 }
221                 else if( args[i].startsWith( "-conditional=") ) {
222                     extractConditionals( args[i].substring(13) ) ;
223                 }      
224                 else if( args[i].startsWith( "-c=") ) {
225                     extractConditionals( args[i].substring(3) ) ;
226                 }                  
227                 
228             }
229             if( BnfExtractor.inFilePath != null ) {
230                 retVal = true ;
231                 if( aConditionals == null ) {
232                     aConditionals = new String[] { V20_PLUS_RFC } ;
233                 }
234                 else if( aConditionals.length == 0 ) {
235                     aConditionals = new String[] { V20_PLUS_RFC } ;
236                 }
237             }
238         }       
239         return retVal ;
240     }
241     
242     private static void extractConditionals( String conditionals ) {
243         if( conditionals == null ) {
244             aConditionals = new String[0] ;
245         }
246         else if( conditionals.length() == 0 ) {
247             aConditionals = new String[0] ;
248         }
249         else {
250             aConditionals = conditionals.split( "," ) ; 
251         } 
252         if( log.isDebugEnabled() ) {
253             StringBuffer b = new StringBuffer() ;
254             b.append( "Conditionals: " ) ;
255             for( int i=0; i<aConditionals.length; i++ ) {
256                 b.append( aConditionals[i] ).append( ' ' ) ;
257             }
258         }
259     }
260     
261     private static boolean isConditional( String c ) {
262         String[] ac = c.split( "," ) ;
263         for( int i=0; i<aConditionals.length; i++ ) {
264             for( int j=0; j<ac.length; j++ ) {
265                 if( aConditionals[i].equalsIgnoreCase( ac[j]) ) 
266                     return true ;
267             }
268         }
269         return false ;
270     }
271     
272     public BnfExtractor( File inputFile, File outputFile, String format, File headerFile ) {
273         if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor(File, File, String)" ) ;
274         this.inputFile = inputFile ;
275         this.outputFile = outputFile ;
276         if( this.outputFile != null ) {
277             try {
278                 this.outputStream = new FileOutputStream( outputFile ) ;
279             }
280             catch( FileNotFoundException fnfex ) {
281                 fnfex.printStackTrace() ;
282             }
283         }
284         else {
285             this.outputStream = System.out ;
286         }   
287         this.iFormatOption = format ;
288         this.headerFile = headerFile ;
289         if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor(File, File, String)" ) ;
290     }
291     
292     public BnfExtractor( File inputFile, String format ) {
293         this( inputFile, null, format, null ) ;
294     }
295    
296     public void exec() {
297         if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor.exec()" ) ;
298         openFiles() ;
299         consumeInputFile() ; 
300         produceOutput() ;
301         if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor.exec()" ) ;
302     }
303     
304     private void openFiles() {
305         if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor.openFiles()" ) ;
306         try {
307             reader = new FileReader( inputFile ) ;
308             headerReader = new FileReader( headerFile ) ;
309         }
310         catch( Exception iox ) {
311             System.out.println( iox.getLocalizedMessage() ) ;
312         }
313         finally {
314             if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor.openFiles()" ) ;
315         }
316     }
317     
318     private void consumeInputFile() {
319         if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor.consumeInputFile()" ) ;
320         while( isInputEOF() == false ) { 
321             processOneStatement() ;
322         }
323         validateInputFile() ;
324 //        BnfStatement[] sArray = getSortedStatementArray() ;       
325 //        if( DEBUG_ENABLED ) {
326 //            for( int i=0; i<sArray.length; i++ ) {
327 //                System.out.println( sArray[i].toString() ) ;
328 //            }
329 //        }
330         if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor.consumeInputFile()" ) ;
331     }
332     
333     private void validateInputFile() {
334         HashMap map = new HashMap() ;
335         HashSet errors = new HashSet() ;
336         ListIterator it = list.listIterator() ;
337         while( it.hasNext() ) {
338             BnfStatement bnf = (BnfStatement)it.next() ;
339             map.put( bnf.key, bnf ) ;
340         }
341         it = list.listIterator() ;
342         while( it.hasNext() ) {
343             BnfStatement bnf = (BnfStatement)it.next() ;
344             checkStatement( bnf, map, errors ) ;
345         }
346         if( errors.size() > 0 ) {
347             String[] errArray = new String[ errors.size() ] ;
348             errArray = (String[])errors.toArray( errArray ) ;
349             Arrays.sort( errArray ) ;
350             for( int i=0; i<errArray.length; i++ ) {
351                 log.error( errArray[i] ) ;
352             }
353         }
354     }
355     
356     private void checkStatement( BnfStatement bnf, HashMap map, HashSet errors ) {
357         String[] rKeys = bnf.getKeysOfReferencedElements() ;
358         for( int i=0; i<rKeys.length; i++ ) {
359             if( !map.containsKey( rKeys[i] ) ) {
360                 errors.add( "BNF Statement missing from input: <" + rKeys[i] + ">" ) ;
361             }
362         }
363     }
364     
365     private void produceOutput() {
366         try {
367             if( iFormatOption.equals( "t" ) ) {
368                 produceText() ;
369             }
370             else {
371                 produceHtml() ;
372             }
373         }
374         catch( IOException iox ) {
375             iox.printStackTrace() ;
376         }
377        
378     }
379     
380     private void processOneStatement() {
381         if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor.processOneStatement()" ) ;     
382         String line = readAheadToStatement() ;
383         String debugLine = line ;
384         try {
385             if( isBnfSingle( line ) ) {
386                 list.add( statementFactory.newInstance( line.substring( BNF_SINGLE.length() ) ) ) ;
387             }
388             else if( isBnfStart( line ) ) {
389                 ArrayList lineArray= new ArrayList() ;               
390                 line = readLine() ;
391                 while( !isInputEOF() && !isBnfEnd( line ) ) {
392                     lineArray.add( line.substring(2) ) ;
393                     line = readLine() ;
394                 }
395                 if( lineArray.size() > 0 ) {
396                     list.add( statementFactory.newInstance( (String[])lineArray.toArray( new String[ lineArray.size() ] ) ) ) ;
397                 }
398             }
399         }
400         catch( InvalidBnfStatementException ibsex ) {
401             System.out.println( ibsex.getLocalizedMessage() ) ;
402         }
403         catch( Throwable ex ) {
404             log.error( "debugLine: " + debugLine, ex ) ;
405         }
406         finally {
407             if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor.processOneStatement()" ) ;
408         }
409     }
410     
411     private String readAheadToStatement() {
412         if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor.readAheadToStatement()" ) ;
413         String line = readLine() ;
414         while( !isInputEOF() && !isBnfTrigger( line ) ) {
415             line = readLine() ;
416         }
417         if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor.readAheadToStatement()" ) ;
418         return line ;
419     }
420     
421     private boolean isBnfTrigger( String line ) {
422         //
423         // bnf single lines can only be unconditional...
424         if( line.startsWith( BNF_SINGLE ) ) {
425             return true ;
426         }
427         //
428         // bnf stretching over more than one line could be conditional...
429         else if( line.startsWith( BNF_START )  ) {           
430             //
431             // But only check conditionals if you have been requested to...
432             if( BnfExtractor.aConditionals.length > 0 ) {                
433                 String line2 = line.trim() ;
434                 //
435                 // If the line does not end with BNF_START, then it must be conditioned...
436                 if( !BNF_START.endsWith( line2 ) ) {
437                     if( isConditional( line2.substring( BNF_START.length()-1 ).trim() ) )  {
438                         return true ;   
439                     }
440                     return false ;
441                 }
442                 return true ;
443             }            
444             return true ;
445         }       
446         return false ;
447         
448     }
449     
450     private String readLine() {
451 //        if( log.isTraceEnabled() ) enterTrace ( "BnfExtractor.readLine()" ) ;
452         emptyLineBuffer() ;
453         try {
454             int ch = reader.read() ;
455             readLoop : {
456                 while( ch != -1 ) {
457                     lineBuffer.append( (char)ch ) ;
458                     if( ch == '\n' ) {
459                         break readLoop ;
460                     }             
461                     ch = reader.read() ;
462                 }
463                 setInputEOF() ;
464             } // end of readLoop
465         }
466         catch( IOException iox ) {
467             System.out.println( iox.getLocalizedMessage() ) ;
468         }
469         finally {
470 //            if( DEBUG_ENABLED ) {
471 //                if( lineBuffer.length() > 0 ) {
472 //                    System.out.print( lineBuffer.toString() ) ;
473 //                }
474 //            }
475 //            if( log.isTraceEnabled() ) exitTrace ( "BnfExtractor.readLine()" ) ;
476         }
477         return lineBuffer.toString() ;
478     }
479     
480     private void produceText() throws IOException {
481        writeHeader() ;
482        BnfStatement[] sArray = getSortedStatementArray() ;  
483        for( int i=0; i<sArray.length; i++ ) {
484            outputStream.write( sArray[i].toText().getBytes() ) ;
485            outputStream.write( '\n' ) ;
486        }
487        outputStream.write( TEXT_FOOTINGS.getBytes() ) ;
488     }
489     
490     private void produceHtml() throws IOException {
491         writeHeader() ;
492         BnfStatement[] sArray = getSortedStatementArray() ;  
493         for( int i=0; i<sArray.length; i++ ) {
494             outputStream.write( sArray[i].toHtml().getBytes() ) ;
495             outputStream.write( '\n' ) ;
496         }
497         outputStream.write( HTML_FOOTINGS.getBytes() ) ;
498      }
499     
500     private void writeHeader() {
501         try {
502                 int ch = headerReader.read() ;
503                 while( ch != -1 ) {
504                     outputStream.write( ch ) ; 
505                     ch = headerReader.read();
506                 }
507         }
508         catch( IOException iox ) {
509             System.out.println( iox.getLocalizedMessage() ) ;
510         }
511         finally {
512 
513         }
514     }
515     
516     private void emptyLineBuffer() {
517         if( lineBuffer.length() > 0 ) {
518             lineBuffer.delete( 0, lineBuffer.length() ) ;
519         }
520     }
521     
522     private void setInputEOF() {
523         this.inputEOF = true ;
524     }
525     
526     private boolean isInputEOF() {
527         return this.inputEOF ;
528     }
529     
530     private boolean isBnfSingle( String line ) {
531         if( line.indexOf( BNF_SINGLE ) != -1 )
532             return true ;
533         return false ;
534     }
535     
536     private boolean isBnfStart( String line ) {
537         if( line.indexOf( BNF_START ) != -1 ) {
538             return true ;
539         }
540         return false ;
541     }
542     
543     private boolean isBnfEnd( String line ) {
544         if( line.indexOf( BNF_END ) != -1 )
545             return true ;
546         return false ;
547     }
548     
549     private BnfStatement[] getSortedStatementArray() {
550         BnfStatement[] arrayStatement = new BnfStatement[ list.size() ] ;
551         arrayStatement = (BnfStatement[])list.toArray( arrayStatement ) ;
552         Arrays.sort( arrayStatement 
553                    , new Comparator() {
554                        public int compare( Object o1, Object o2 ) {
555                            return ((BnfStatement)o1).compare((BnfStatement)o2) ;
556                        }       
557                    } ) ;
558         return arrayStatement ;
559     }
560     
561     class BnfStatement {
562          
563         String statementsAsString ;
564         String key ;
565         
566         BnfStatement( String[] statements, String key ) {
567             if( log.isDebugEnabled() ) {
568                 log.debug( "BnfStatement.key: " + key ) ;
569             }
570             this.key = key ;
571             StringBuffer buffer = new StringBuffer() ;
572             for( int i=0; i<statements.length; i++ ) {
573                 buffer.append( statements[i] ) ;
574             }
575             this.statementsAsString = buffer.toString() ;
576         }
577         
578         private BnfStatement() {}
579         
580         /* (non-Javadoc)
581          * @see java.lang.Object#equals(java.lang.Object)
582          */
583         public boolean equals( Object that ) {
584             if( that instanceof BnfStatement ) {
585                 BnfStatement arg = (BnfStatement)that ;
586                 if( this.key.equals( arg.key ) ) 
587                     return true ;
588             }        
589             return false ;
590         }
591         
592         public int compare( BnfStatement s ) {
593             return this.key.compareTo( s.key ) ;          
594         }
595         
596         public String toString() {
597             return statementsAsString ;
598         }
599         
600         public String toText() {
601             return toString() ;
602         }
603         
604         public String[] getKeysOfReferencedElements() {
605             ArrayList elements = new ArrayList() ;
606             ArrayList psList = getMatchedPairsAndSingletons() ;
607             ListIterator it = psList.listIterator() ;
608             //
609             // Read past my own key...
610             it.next() ;
611             while( it.hasNext() ) {
612                 Object o = it.next() ;
613                 if( o instanceof Pair ) {
614                     Pair p = (Pair)o ;
615                     String elementName = statementsAsString.substring( p.x+1, p.y ) ;
616                     elements.add( elementName ) ;
617                 } 
618             }
619             String[] elementArray = new String[ elements.size() ] ;
620             elementArray = (String[])elements.toArray( elementArray ) ;
621             return elementArray ;
622         }
623         
624         public String toHtml() {  
625             String s = statementsAsString ;
626             StringBuffer buffer = new StringBuffer() ;
627             ArrayList psList = getMatchedPairsAndSingletons() ;
628             ListIterator it = psList.listIterator() ;
629             //
630             // There has to be a first one and it has to be a Pair...
631             // It is the element or key!
632             Pair p = (Pair)it.next() ;
633             //
634             // First, buffer up the space before the element name...
635             buffer.append( s.substring( 0, p.x ) ) ;
636             //
637             // Format the element/key...      
638             String elementName = s.substring( p.x+1, p.y ) ;
639             String  htmlizedElement = 
640               keyFormat.format( new String[]{elementName}, new StringBuffer(), null ).toString() ;
641             buffer.append( htmlizedElement ) ;
642             //
643             // OK, now for the details...
644             // But first save the current position.
645             int current = p.y + 1 ;
646             while( it.hasNext() ) {
647                 Object o = it.next() ;
648                 if( o instanceof Pair ) {
649                     p = (Pair)o ;
650                     // Buffer up the intermediate characters...
651                     buffer.append( s.substring( current, p.x ) ) ;
652                     // Then format one detail element...
653                     elementName = s.substring( p.x+1, p.y ) ;
654                     htmlizedElement = 
655                         detailFormat.format( new String[]{elementName}, new StringBuffer(), null ).toString() ;
656                     buffer.append( htmlizedElement ) ;            
657                     current = p.y + 1 ; // remember to hold position
658                 }
659                 else { // It must be a singleton...
660                     int i = ((Integer)o).intValue() ;
661                     // Buffer up the intermediate characters...
662                     if( current < i ) {
663                         buffer.append( s.substring( current, i ) ) ;
664                         current = i ;
665                     }                   
666                     if( s.charAt(i) == '>' ) {
667                         buffer.append( "&gt;" ) ;
668                     }
669                     else {
670                         buffer.append( "&lt;" ) ;
671                     }
672                     current++ ; // remember to hold position
673                 }
674             }
675             //
676             // Pick up the remainder...
677             // which may include comments with < and > embedded in them...
678             if( current != s.length() ) {
679                 String remainder = s.substring( current ) ;
680                 if( log.isDebugEnabled() ) {
681                     log.debug( "Remainder: " + remainder ) ;
682                 }
683                 remainder = remainder.replaceAll( "<", "&lt;" ) ;
684                 remainder = remainder.replaceAll( ">", "&gt;" ) ;
685                 buffer.append( remainder ) ;
686             }  
687             return buffer.toString() ;
688         }
689         
690         private ArrayList getMatchedPairsAndSingletons() {
691             ArrayList psList = new ArrayList() ;
692             ArrayList indices = getIndicesOfLtGt() ;
693             Integer[] x = new Integer[ indices.size() ] ;
694             String s = statementsAsString ;
695             x = (Integer[])indices.toArray( x ) ;
696             Integer candidateLt = null ;
697             Integer candidateGt = null ;
698             for( int i=0; i<x.length; i++ ) {
699                 if( s.charAt(x[i].intValue() ) == '<' ) {
700                     if( candidateLt == null ) {
701                         candidateLt = x[i] ;
702                     }
703                     else {
704                         psList.add( candidateLt ) ;
705                         candidateLt = x[i] ;
706                     }
707                 }
708                 else if( s.charAt(x[i].intValue() ) == '>' ){
709                     if( candidateGt == null ) {
710                         candidateGt = x[i] ;
711                     }
712                     else {
713                         psList.add( x[i] ) ;
714                     }
715                 }
716                 if( candidateLt != null && candidateGt != null ) {
717                     if( candidateLt.intValue()+1 == candidateGt.intValue() ) {
718                         psList.add( candidateLt ) ;
719                         psList.add( candidateGt ) ;
720                     }
721                     else {
722                         psList.add( new Pair( candidateLt, candidateGt ) ) ;
723                     }
724                     candidateLt = candidateGt = null ;
725                 }
726             }
727             return psList ;
728         }
729         
730         private ArrayList getIndicesOfLtGt() {
731             ArrayList listLtGt = new ArrayList() ;
732             for( int i=0; i<statementsAsString.length(); i++ ) {
733                 char c = statementsAsString.charAt(i) ;
734                 if( c == '!' && statementsAsString.charAt(i+1) == '!' ) {
735                     break ;
736                 }
737                 if( c == '<' || c == '>' )
738                     listLtGt.add( new Integer(i) ) ;
739             }
740             return listLtGt ;
741         }
742         
743     }
744     
745     class BnfStatementFactory {
746         
747         BnfStatement newInstance( String statement ) throws InvalidBnfStatementException {
748             return new BnfStatement( new String[] { statement }, extractKey( statement ) ) ;
749         }
750         
751         BnfStatement newInstance( String[] statementArray ) throws InvalidBnfStatementException {
752             return new BnfStatement( statementArray, extractKey( statementArray[0] ) ) ;
753         }
754         
755         private String extractKey( String firstLine ) throws InvalidBnfStatementException {
756             int indexFrom = firstLine.indexOf( '<' ) ;
757             int indexTo = firstLine.indexOf(  '>', indexFrom ) ;
758             int indexOfDefinitionOp = firstLine.indexOf( "::=", indexTo ) ;
759             
760             if( indexFrom == -1 || indexTo == -1 || indexOfDefinitionOp == -1 ) {
761                 String message = 
762                     "InvalidBnfStatementException:\n" +
763                     "   " + firstLine ;
764                 throw new InvalidBnfStatementException( message ) ;
765             }
766             
767             return firstLine.substring( indexFrom+1, indexTo ) ;
768         }
769         
770     }
771     
772     class InvalidBnfStatementException extends Exception {
773         public InvalidBnfStatementException(String message) {
774             super(message);
775         }       
776     }
777     
778     class Pair {
779         public int x ;
780         public int y ;
781         
782         Pair( Integer X, Integer Y ) {
783             x = X.intValue() ;
784             y = Y.intValue() ;
785         }
786     }
787   
788     private static void enterTrace( String entry ) {
789         log.debug( logIndent.toString() + "enter: " + entry ) ;
790         indentPlus() ;
791     }
792 
793     private static void exitTrace( String entry ) {
794         indentMinus() ;
795         log.debug( logIndent.toString() + "exit : " + entry ) ;
796     }
797     
798     private static void indentPlus() {
799         logIndent.append( ' ' ) ;
800     }
801     
802     private static void indentMinus() {
803         logIndent.deleteCharAt( logIndent.length()-1 ) ;
804     }
805 
806 }
807 
808 
809 /*
810 $Log: BnfExtractor.java,v $
811 Revision 1.2  2008/09/08 15:37:23  jl99
812 Merge of branch adql_jl_2575_mark2 into HEAD
813 
814 Revision 1.1.2.3  2008/09/08 11:16:20  jl99
815 Change to conditional bnf tag.
816 
817 Revision 1.1.2.2  2008/08/29 19:28:59  jl99
818 Improvements to javadoc and usage statement
819 
820 Revision 1.1.2.1  2008/08/29 14:49:11  jl99
821 First mass commit for the new project adql2
822 
823 Revision 1.5.2.9  2008/08/28 18:24:07  jl99
824 Correction to command line arg processing
825 
826 Revision 1.5.2.8  2008/08/28 11:11:39  jl99
827 Headers (HTML or TEXT) now in separate files.
828 
829 Revision 1.5.2.7  2008/08/28 10:55:46  jl99
830 Headers now in a separate file.
831 Conditionals can be aggregated.
832 
833 Revision 1.5.2.6  2008/07/21 13:29:58  jl99
834 conditional processing in BNF utility
835 
836 Revision 1.5.2.5  2008/07/21 13:24:36  jl99
837 conditional processing in BNF utility
838 
839 Revision 1.5.2.4  2008/06/03 15:17:52  jl99
840 New type safety(-ish) approach to geometry functions.
841 
842 Revision 1.5.2.3  2008/06/03 10:05:27  jl99
843 Code tidy
844 
845 Revision 1.5.2.2  2008/05/07 14:12:47  jl99
846 tidy
847 
848 Revision 1.5.2.1  2008/02/27 12:40:28  jl99
849 Changes to Region
850 
851 Revision 1.5  2007/07/30 09:04:14  jl99
852 BNF tidy
853 
854 Revision 1.4  2007/07/29 11:28:34  jl99
855 tidy
856 
857 Revision 1.3  2007/07/16 21:09:25  jl99
858 *** empty log message ***
859 
860 Revision 1.2  2007/07/12 13:42:27  jl99
861 Changed top comments on emitted documentation.
862 
863 Revision 1.1  2007/06/28 09:07:48  jl99
864 Creation of temporary project adql2 to explore complexities of moving
865 ADQL to conform to the draft spec of April 2007.
866 
867 Revision 1.9  2007/06/06 18:20:19  jl99
868 Merge of branch adql-jl-2135
869 
870 Revision 1.8.8.1  2007/06/06 10:53:59  jl99
871 Code tidy just prior to merge of branch adql-jl-2135
872 
873 Revision 1.8  2006/11/12 20:06:44  jl99
874 Slight change to heading.
875 
876 Revision 1.7  2006/11/06 23:08:38  jl99
877 Removed footnote references.
878 
879 Revision 1.6  2006/10/28 22:10:19  jl99
880 Adjustments in the area of aliased expressions.
881 At present ADQL/x is broken in this area.
882 
883 Revision 1.5  2006/10/25 11:47:16  jl99
884 Corrections to processing not-equals-operator in html format (ie: <>)
885 
886 Revision 1.4  2006/10/23 22:11:53  jl99
887 Validates the input file for missing elements.
888 
889 Revision 1.3  2006/10/23 21:13:46  jl99
890 Working correctly to file and standard out.
891 Produces text and html versions.
892 
893 Revision 1.2  2006/10/23 09:43:55  jl99
894 Working correctly to standard out.
895 
896 Revision 1.1  2006/10/22 22:03:55  jl99
897 First commit of utility to extract bnf statements from annotated .jjt files.
898 
899 */