Project Report: fawkez

Packagesummary org.jcoderz.phoenix.sqlparser

org.jcoderz.phoenix.sqlparser.SqlParser

LineHitsNoteSource
1  /*
2   * $Id: SqlParser.java 1264 2008-12-12 13:32:27Z amandel $
3   *
4   * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5   *
6   * Redistribution and use in source and binary forms, with or without
7   * modification, are permitted provided that the following conditions are
8   * met:
9   *
10   *    * Redistributions of source code must retain the above copyright
11   *      notice, this list of conditions and the following disclaimer.
12   *    * Redistributions in binary form must reproduce the above
13   *      copyright notice, this list of conditions and the following
14   *      disclaimer in the documentation and/or other materials
15   *      provided with the distribution.
16   *    * Neither the name of the jCoderZ.org Project nor the names of
17   *      its contributors may be used to endorse or promote products
18   *      derived from this software without specific prior written
19   *      permission.
20   *
21   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24   * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25   * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32   */
33  package org.jcoderz.phoenix.sqlparser;
34  
35  import java.io.FileInputStream;
36  import java.util.ArrayList;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Stack;
40  import java.util.logging.Level;
41  import java.util.logging.Logger;
42  
43  /**
44   * @author Albrecht Messner
45   */
46  public class SqlParser
47  {
4875    private static final String CLASSNAME = SqlParser.class.getName();
49100    private static final Logger logger = Logger.getLogger(CLASSNAME);
50  
51100    private static final SpecialColumnComment TIMESTAMP_SPECIAL_COMMENT
52           = new SpecialColumnComment();
53  
54100    private static final SpecialColumnComment DATE_SPECIAL_COMMENT
55           = new SpecialColumnComment();
56  
57     static
58     {
59        Token t;
60        try
61        {
62100          t = new Token(TokenType.COMMENT,
63                 "-- @cmpgen.java-type=org.jcoderz.commons.types.Date");
64100          TIMESTAMP_SPECIAL_COMMENT.parseComment(t);
65100          DATE_SPECIAL_COMMENT.parseComment(t);
66  
67100          t = new Token(TokenType.COMMENT,
68                 "-- @cmpgen.store-method=toSqlTimestamp()");
69100          TIMESTAMP_SPECIAL_COMMENT.parseComment(t);
70100          t = new Token(TokenType.COMMENT,
71                 "-- @cmpgen.store-method=toSqlDate()");
72100          DATE_SPECIAL_COMMENT.parseComment(t);
73  
74100          t = new Token(TokenType.COMMENT,
75                 "-- @cmpgen.load-method=fromSqlTimestamp(java.sql.Timestamp)");
76100          TIMESTAMP_SPECIAL_COMMENT.parseComment(t);
77100          t = new Token(TokenType.COMMENT,
78                 "-- @cmpgen.load-method=fromSqlDate(java.sql.Date)");
79100          DATE_SPECIAL_COMMENT.parseComment(t);
80        }
810       catch (ParseException e)
82        {
830          throw new RuntimeException("This must not happen!", e);
84100       }
85100    }
86  
87     private final ScannerInterface mScanner;
88  
89100    private final Stack mTokenStack = new Stack();
90  
91     private SpecialColumnComment mSpecialColumnComment;
92     private SpecialAnnotationComment mColumnAnnotation;
93  
94100    private final List mSpecialStatementComments = new ArrayList();
95     private SpecialAnnotationComment mStatementAnnotation;
96  
97     /**
98      * Create a new SQL Parser.
99      *
100      * @param scanner the SQL Scanner that delivers tokens to this parser
101      */
102     public SqlParser (ScannerInterface scanner)
103100    {
104100       mScanner = scanner;
105100       mScanner.setReportWhitespace(false);
106100    }
107  
108     /**
109      * Wrapper method for ScannerInterface.nextToken().
110      *
111      * @return returns the next token from the stack of the scanner,
112      *       or null if the stack is empty,
113      *
114      * @throws ParseException
115    */
116     private Token getNextToken () throws ParseException
117     {
118        Token result;
119100       if (mTokenStack.isEmpty())
120        {
121100          result = mScanner.nextToken();
122        }
123        else
124        {
125100          result = (Token) mTokenStack.pop();
126        }
127  
128100       if (logger.isLoggable(Level.FINER))
129        {
1300          final Exception x = new Exception();
1310          int index = 1;
1320          final StackTraceElement caller = x.getStackTrace()[index];
1330          StackTraceElement callerParent = null;
134           try
135           {
1360             callerParent = x.getStackTrace()[++index];
137           }
1380          catch (ArrayIndexOutOfBoundsException x2)
139           {
140              // ignore, the stack is simply not long enough
1410          }
142  
1430          logger.finest("TOKEN: " + result + " to " + caller
144                 + " called by " + callerParent);
145        }
146  
147100       return result;
148     }
149  
150     /**
151      * gives parser a chance to return a token if it has read too much
152      *
153      * @param token
154    */
155     private void pushToken (Token token)
156     {
157100       mTokenStack.push(token);
158100    }
159  
160     /**
161      * gives parser a chance to return a token if it has read too much,
162      * the returned token is converted into an identifier if possible.
163      *
164      * @param token
165    * @throws ParseException
166    */
167     private void pushTokenAsIdentifier (Token token) throws ParseException
168     {
169100        Token pushToken = null;
170100        if (token.getType() == TokenType.IDENTIFIER)
171         {
172100            pushToken = token;
173         }
1740        else if (token.getType() == TokenType.SEQUENCE)
175         {
1760            pushToken = new Token(TokenType.IDENTIFIER, token.getValue());
177         }
178         else
179         {
1800            unexpectedToken(token);
181         }
182100       mTokenStack.push(pushToken);
183100    }
184  
185     /**
186      * Read tokens from the scanner until EOF and parse them.
187      *
188      * @return a list of parsed SQL statements
189      * @throws ParseException if an error occurs
190      */
191     public final List parse ()
192        throws ParseException
193     {
194100       final List tableObjects = new ArrayList();
195100       final List indexObjects = new ArrayList();
196100       final List sequenceObjects = new ArrayList();
197  
198        Token token;
199100(1)      while ((token = getNextToken()).getType() != TokenType.EOF)
200        {
201100          final SqlStatement stmt = handleStartOfStatement(token);
202100          if (stmt != null)
203           {
204100(2)            if (stmt instanceof CreateTableStatement)
205              {
206100                tableObjects.add(stmt);
207              }
208100             else if (stmt instanceof CreateIndexStatement)
209              {
210100                indexObjects.add(stmt);
211              }
212100             else if (stmt instanceof CreateSequenceStatement)
213              {
214100                sequenceObjects.add(stmt);
215              }
216100             logger.info("Parsed statement: " + stmt);
217           }
218100       }
219  
220100       for (final Iterator it = indexObjects.iterator(); it.hasNext(); )
221        {
222100          final CreateIndexStatement stmt = (CreateIndexStatement) it.next();
223100          final String tname = stmt.getTableName();
224100          boolean found = false;
225100          for (final Iterator it2 = tableObjects.iterator(); it2.hasNext(); )
226           {
227100             final CreateTableStatement stmt2
228                  = (CreateTableStatement) it2.next();
229100             if (stmt2.getTableName().equalsIgnoreCase(tname))
230              {
231100                stmt2.addIndex(stmt);
232100                found = true;
233100                break;
234              }
235100          }
236  
237100          if (! found)
238           {
2390             throw new ParseException(
240                    "Index on table " + tname
241                    + " declared, but create statement for table " + tname
242                    + " not found", -1, -1);
243           }
244100       }
245  
246100       final List result = new ArrayList();
247100       result.addAll(tableObjects);
248100       result.addAll(sequenceObjects);
249100       return result;
250     }
251  
252     private SqlStatement handleStartOfStatement (Token token)
253        throws ParseException
254     {
255100       SqlStatement result = null;
256100       if (token.getType() == TokenType.CREATE)
257        {
258100          logger.finer("Start parsing CREATE statement");
259100          result = handleCreateStatement();
260        }
261100       else if (token.getType() == TokenType.ALTER
262              || token.getType() == TokenType.DROP
263              || token.getType() == TokenType.INSERT
264              || token.getType() == TokenType.DELETE
265              || token.getType() == TokenType.SELECT)
266        {
267100          logger.info("Skipping " + token.getValue() + " statement");
268100          eatUntilSemicolon();
269        }
270100       else if (token.getType() == TokenType.COMMENT)
271        {
272100          pushToken(token);
273100          parseStatementComment();
274        }
275        else
276        {
2770          logger.finer("Unable to handle token " + token);
2780(3)         throw new ParseException(
279              "Unexpected token",
280              mScanner.getLine(),
281              mScanner.getColumn());
282        }
283100       return result;
284     }
285  
286 (4)(5)   private SqlStatement handleCreateStatement ()
287        throws ParseException
288     {
289100       final String methodName = "handleCreateStatement()";
290100       logger.entering(CLASSNAME, methodName);
291  
292  
293100       Token token = getNextToken();
294100       SqlStatement result = null;
295  
296100       final TokenType type = token.getType();
297  
298        // we can only parse CREATE TABLE and CREATE INDEX statements
299100       if (type == TokenType.TABLE)
300        {
301100          token = getNextToken();
302           // sequence is a valid identifier...
30350          if (token.getType().equals(TokenType.IDENTIFIER)
304               || token.getType().equals(TokenType.SEQUENCE))
305           {
306100              result = new CreateTableStatement(token.getValue());
307           }
308           else
309           {
3100              unexpectedToken(token);
311           }
312  
313  
314100          assertNextToken(TokenType.OPEN_PAREN);
315  
316           do
317           {
318100             parseRelationalPropOrComment((CreateTableStatement) result);
319  
320100             token = getNextToken();
321100             if (token.getType() == TokenType.CLOSE_PAREN)
322              {
323                 // we've reached the end of the relational properties
324100                break;
325              }
326100             else if (token.getType() == TokenType.COMMENT)
327              {
328100                pushToken(token);
329100                continue;
330              }
331              else
332              {
333100                pushToken(token);
334100                continue;
335              }
336           }
337           while (true);
338  
339100          eatUntilSemicolon();
340  
341100(6)         if (mSpecialStatementComments.size() > 0)
342           {
3430             for (final Iterator it = mSpecialStatementComments.iterator();
3440                   it.hasNext(); )
345              {
3460                final SpecialStatementComment comment
347                       = (SpecialStatementComment) it.next();
3480                if (comment.getType() == SpecialStatementComment.TYPE_BEAN_NAME)
349                 {
3500                   ((CreateTableStatement) result).setBeanName(
351                          comment.getContent());
352                 }
3530                else if (
354                       comment.getType() == SpecialStatementComment.TYPE_JAVADOC)
355                 {
3560                   ((CreateTableStatement) result).setAdditionalJavadoc(
357                          comment.getContent());
358                 }
3590                else if (comment.getType()
360                       == SpecialStatementComment.TYPE_OPTIMISTIC_VERSION_COUNT)
361                 {
3620                   ((CreateTableStatement) result).
363                          setOptimisticVersionCount(true);
364                 }
3650                else if (comment.getType()
366                       == SpecialStatementComment.TYPE_SKIP_APPSERVER_SUPPORT)
367                 {
3680                   ((CreateTableStatement) result).
369                          setSkipAppserverSupport(true);
370                 }
371  
3720             }
3730             mSpecialStatementComments.clear();
374           }
375        }
376100       else if (type == TokenType.UNIQUE
377              || type == TokenType.BITMAP
378              || type == TokenType.INDEX)
379        {
380100          boolean isUnique = false;
381100          if (type == TokenType.UNIQUE)
382           {
383100             assertNextToken(TokenType.INDEX);
384100             isUnique = true;
385           }
3860          else if (type == TokenType.BITMAP)
387           {
3880             assertNextToken(TokenType.INDEX);
389           }
390  
391100          token = assertNextToken(TokenType.IDENTIFIER);
392100          final CreateIndexStatement stmt
393               = new CreateIndexStatement(token.getValue());
394100          stmt.setUnique(isUnique);
395100          parseCreateIndexStatement(stmt);
396100          result = stmt;
397100          eatUntilSemicolon();
398100       }
399100       else if (type == TokenType.SEQUENCE)
400        {
401100          token = assertNextToken(TokenType.IDENTIFIER);
402100          final CreateSequenceStatement stmt
403                 = new CreateSequenceStatement(token.getValue());
404  
405100          parseCreateSequenceStatement(stmt);
406  
407100          result = stmt;
408100          eatUntilSemicolon();
409  
410100          logger.info("Parsed sequence: " + stmt);
411100       }
412        else
413        {
4140          logger.info("Can't parse create statement for "
415                 + token.getValue() + ", skipping.");
4160          eatUntilSemicolon();
417        }
418  
419100       if (mStatementAnnotation != null)
420        {
4210          result.setAnnotation(mStatementAnnotation.getAnnotation());
4220          mStatementAnnotation = null;
423        }
424  
425100       logger.exiting(CLASSNAME, methodName, result);
426100       return result;
427     }
428  
429 (7)   private void parseCreateSequenceStatement (CreateSequenceStatement stmt)
430           throws ParseException
431     {
432        Token token;
433100(8)      while ((token = getNextToken()).getType() != TokenType.SEMICOLON)
434        {
435100          final TokenType type = token.getType();
436100          if (type == TokenType.INCREMENT)
437           {
438100             assertNextToken(TokenType.BY);
439100             token = assertNextToken(TokenType.NUMERIC_LITERAL);
440100             stmt.setIncrementBy(Long.parseLong(token.getValue()));
441           }
442100          else if (type == TokenType.START)
443           {
444100             assertNextToken(TokenType.WITH);
445100             token = assertNextToken(TokenType.NUMERIC_LITERAL);
446100(9)            stmt.setStartWith(new Long(Long.parseLong(token.getValue())));
447           }
448100          else if (type == TokenType.MAXVALUE)
449           {
4500             token = assertNextToken(TokenType.NUMERIC_LITERAL);
4510(10)            stmt.setMaxValue(new Long(Long.parseLong(token.getValue())));
452           }
453100          else if (type == TokenType.NOMAXVALUE)
454           {
4550             stmt.setNoMaxValue(true);
456           }
457100          else if (type == TokenType.MINVALUE)
458           {
4590             token = assertNextToken(TokenType.NUMERIC_LITERAL);
4600(11)            stmt.setMinValue(new Long(Long.parseLong(token.getValue())));
461           }
462100          else if (type == TokenType.NOMINVALUE)
463           {
4640             stmt.setNoMinValue(true);
465           }
466100          else if (type == TokenType.CYCLE)
467           {
4680             stmt.setCycle(true);
469           }
470100          else if (type == TokenType.NOCYCLE)
471           {
472100             stmt.setCycle(false);
473           }
474100          else if (type == TokenType.CACHE)
475           {
4760             token = assertNextToken(TokenType.NUMERIC_LITERAL);
4770             stmt.setCache(Long.parseLong(token.getValue()));
478           }
479100          else if (type == TokenType.NOCACHE)
480           {
481100             stmt.setCache(0);
482           }
483100          else if (type == TokenType.ORDER)
484           {
485100             stmt.setOrder(true);
486           }
4870          else if (type == TokenType.NOORDER)
488           {
4890             stmt.setOrder(false);
490           }
491           else
492           {
4930             unexpectedToken(token);
494           }
495100       }
496  
497        // put the semicolon back on the stack
498100       pushToken(token);
499100    }
500  
501     private void parseCreateIndexStatement (CreateIndexStatement result)
502           throws ParseException
503     {
504        // the CREATE (UNIQUE|BITMAP) INDEX <name> part has already been eaten.
505100       assertNextToken(TokenType.ON);
506100       Token token = assertNextToken(TokenType.IDENTIFIER);
507100       result.setTableName(token.getValue());
508  
509100       assertNextToken(TokenType.OPEN_PAREN);
510  
511        while (true)
512        {
513100          token = assertNextToken(TokenType.IDENTIFIER);
514100          result.addColumn(token.getValue());
515  
516100          token = getNextToken();
517100          if (token.getType() == TokenType.CLOSE_PAREN)
518           {
519100             break;
520           }
5210          else if (! (token.getType() == TokenType.COMMA))
522           {
5230             unexpectedToken(token);
524           }
525        }
526100    }
527  
528     /**
529      * Parse a single column spec, or an out-of-line constraint.
530      */
531     private void parseRelationalPropOrComment (CreateTableStatement createStmt)
532        throws ParseException
533     {
534100       final Token token = getNextToken();
535100       if (isStartOfOutOfLineConstraint(token))
536        {
537           // delegate
538100          pushToken(token);
539100          parseOutOfLineConstraint(createStmt);
540        }
541        // sequence is a valid identifier
542100       else if (token.getType() == TokenType.IDENTIFIER
543            || token.getType() == TokenType.SEQUENCE)
544        {
545           // delegate
546100          pushTokenAsIdentifier(token);
547100          parseColumnSpec(createStmt);
548        }
549100       else if (token.getType() == TokenType.COMMENT)
550        {
551           // delegate
552100          pushToken(token);
553100          parseColumnComment();
554        }
555        else
556        {
5570          unexpectedToken(token);
558        }
559100    }
560  
561 (12)(13)   private void parseColumnSpec (CreateTableStatement createStmt)
562           throws ParseException
563     {
564100       final ColumnSpec colSpec = new ColumnSpec();
565  
566        // column name
567100       Token token = assertNextToken(TokenType.IDENTIFIER);
568100       colSpec.setColumnName(token.getValue());
569  
570        // data type
571100       token = assertNextToken(TokenType.IDENTIFIER);
572100       colSpec.setColumnType(token.getValue());
573  
574100(14)      if (token.getValue().equalsIgnoreCase("TIMESTAMP")
575              && mSpecialColumnComment == null)
576        {
577100          mSpecialColumnComment = TIMESTAMP_SPECIAL_COMMENT;
578        }
579100(15)      else if (token.getValue().equalsIgnoreCase("DATE")
580              && mSpecialColumnComment == null)
581        {
582100          mSpecialColumnComment = DATE_SPECIAL_COMMENT;
583        }
584  
585100       token = getNextToken();
586100       if (token.getType() == TokenType.OPEN_PAREN)
587        {
588100          token = assertNextToken(TokenType.NUMERIC_LITERAL);
589100          colSpec.addDatatypeAttribute(new NumericAttribute(token.getValue()));
590100          token = getNextToken();
591100          if (token.getType() == TokenType.IDENTIFIER)
592           {
5930             colSpec.addDatatypeAttribute(
594                  new StringAttribute(token.getValue()));
5950             assertNextToken(TokenType.CLOSE_PAREN);
596           }
597100          else if (token.getType() == TokenType.COMMA)
598           {
599100             token = assertNextToken(TokenType.NUMERIC_LITERAL);
600100             colSpec.addDatatypeAttribute(
601                      new NumericAttribute(token.getValue()));
602100             assertNextToken(TokenType.CLOSE_PAREN);
603           }
604100          else if (token.getType() != TokenType.CLOSE_PAREN)
605           {
6060             throw new ParseException("Unexpected token: " + token,
607                 mScanner.getLine(), mScanner.getColumn());
608           }
609        }
610        else
611        {
612100          pushToken(token);
613        }
614  
615100       parseColumnAttributes(colSpec, createStmt);
616  
617100       token = getNextToken();
618100       if (token.getType() != TokenType.COMMA)
619        {
620100          pushToken(token);
621        }
622  
623        // at this point the full colspec is parsed
624100       if (mSpecialColumnComment != null)
625        {
626100          mSpecialColumnComment.validate();
627100          colSpec.setJavaType(mSpecialColumnComment.getJavaType());
628100          colSpec.setLoadMethod(mSpecialColumnComment.getLoadMethod());
629100          colSpec.setStoreMethod(mSpecialColumnComment.getStoreMethod());
630100          colSpec.setCurrencyColumn(mSpecialColumnComment.getCurrencyColumn());
631  
63260          colSpec.setIsPeriodDefined(
633                 mSpecialColumnComment.getPeriodFieldName() != null
634                 && mSpecialColumnComment.getPeriodEndDateColumn() != null);
635100          colSpec.setPeriodFieldName(
636                 mSpecialColumnComment.getPeriodFieldName());
637100          colSpec.setPeriodEndDateColumn(
638                 mSpecialColumnComment.getPeriodEndDateColumn());
639100          colSpec.setWeblogicColumnType(
640                 mSpecialColumnComment.getWeblogicColumnType());
641  
642100          colSpec.setSkipInInterface(mSpecialColumnComment.isSkipInInterface());
643100          mSpecialColumnComment = null;
644        }
645100       if (mColumnAnnotation != null)
646        {
6470          colSpec.setAnnotation(mColumnAnnotation.getAnnotation());
6480          mColumnAnnotation = null;
649        }
650  
651100       createStmt.addColumn(colSpec);
652100    }
653  
654     private void parseStatementComment ()
655           throws ParseException
656     {
657100       final Token token = assertNextToken(TokenType.COMMENT);
658100       final String val = token.getValue();
659  
660100       if (val.indexOf("@cmpgen") != -1)
661        {
6620          final SpecialStatementComment comment
663                 = SpecialStatementComment.parseComment(token);
6640          if (comment != null)
665           {
6660             mSpecialStatementComments.add(comment);
667           }
6680       }
669100       else if (val.indexOf("@@annotation") != -1)
670        {
6710          if (mStatementAnnotation == null)
672           {
6730             mStatementAnnotation = new SpecialAnnotationComment();
674           }
6750          mStatementAnnotation.parseComment(token);
676        }
677100    }
678  
679  
680     private void parseColumnComment () throws ParseException
681     {
682100       final Token token = assertNextToken(TokenType.COMMENT);
683100       final String str = token.getValue();
684100       if (str.indexOf("@cmpgen") != -1)
685        {
686100          if (mSpecialColumnComment == null)
687           {
688100             mSpecialColumnComment = new SpecialColumnComment();
689           }
690100          mSpecialColumnComment.parseComment(token);
691        }
692100       else if (str.indexOf("@@annotation") != -1)
693        {
6940          if (mColumnAnnotation != null)
695           {
6960             throw new ParseException("Only one annotation per column allowed",
697                    mScanner.getLine(), mScanner.getColumn());
698           }
6990          mColumnAnnotation = new SpecialAnnotationComment();
7000          mColumnAnnotation.parseComment(token);
701        }
702        else
703        {
704100          logger.finer("Ignoring non-special comment " + token);
705        }
706100    }
707  
708     /**
709      * parse a set of column attributes.
710      *
711      * this is supposed to parse everything after the column name and data type,
712      * namely:
713      * <ul>
714      *    <li>a default value for the column</li>
715      *    <li>inline constraints</li>
716      * </ul>
717      */
718 (16)   private void parseColumnAttributes (
719           ColumnSpec colSpec, CreateTableStatement stmt)
720           throws ParseException
721     {
722        Token token;
723  
724100       token = getNextToken();
725100       TokenType type = token.getType();
726100       if (type == TokenType.COMMA
727              || type == TokenType.CLOSE_PAREN)
728        {
729100          pushToken(token);
730        }
731100       else if (type == TokenType.DEFAULT)
732        {
733           // default clause: DEFAULT expr
734           // read until end of EXPR, which is either a comma,
735           // a closing parentheses or the start of an inline constraint
736100          final StringBuffer defltExpr = new StringBuffer();
737100          mScanner.setReportWhitespace(true);
738           while (true)
739           {
740100             token = getNextToken();
741100             type = token.getType();
742100             if (type == TokenType.OPEN_PAREN)
743              {
744100                defltExpr.append(token.getValue());
745100                defltExpr.append(eatUntilClosingParen());
746100                defltExpr.append(
747                       assertNextToken(TokenType.CLOSE_PAREN).getValue());
748              }
749100             else if (type == TokenType.COMMA
750                    || type == TokenType.CLOSE_PAREN
751                    || isStartOfInlineConstraint(token))
752              {
753100                pushToken(token);
754100                break;
755              }
756              else
757              {
758100                defltExpr.append(token.getValue());
759              }
760           }
761100          mScanner.setReportWhitespace(false);
762100          final DefaultClause dfltClause
763               = new DefaultClause(defltExpr.toString());
764100          colSpec.addAttribute(dfltClause);
765100       }
766100       else if (isStartOfInlineConstraint(token))
767        {
768           // return the token we just read,
769           // because it's the start of a constraint
770100          pushToken(token);
771100          parseInlineConstraint(colSpec, stmt);
772        }
773        else
774        {
7750          unexpectedToken(token);
776        }
777  
778        while (true)
779        {
780100          token = getNextToken();
781100          logger.finer("Checking for more inline constraints: got " + token);
782100          pushToken(token);
783100          if (isStartOfInlineConstraint(token))
784           {
785100             parseInlineConstraint(colSpec, stmt);
786           }
787100          else if (token.getType() == TokenType.COMMA
788                 || token.getType() == TokenType.CLOSE_PAREN)
789           {
790100             break;
791           }
792           else
793           {
7940             unexpectedToken(token);
795           }
796        }
797100    }
798  
799 (17)   private void parseInlineConstraint (
800           ColumnSpec column, CreateTableStatement stmt)
801           throws ParseException
802     {
803        Token token;
804        TokenType type;
805  
806100       String constraintName = "unnamed";
807100       token = getNextToken();
808100       type = token.getType();
809  
810100       if (type == TokenType.CONSTRAINT)
811        {
812100          token = assertNextToken(TokenType.IDENTIFIER);
813100          constraintName = token.getValue();
814        }
815        else
816        {
817100          pushToken(token);
818        }
819  
820100       token = getNextToken();
821100       type = token.getType();
822100       if (type == TokenType.NOT)
823        {
824100          assertNextToken(TokenType.NULL);
825100          column.setNotNull(true);
826100(18)         logger.finer("Parsed inline constraint " + constraintName + ": "
827                 + column.getColumnName() + " is not nullable");
828        }
829100       else if (type == TokenType.NULL)
830        {
8310          column.setNotNull(false);
8320          logger.finer("Parsed inline constraint " + constraintName + ": "
833                 + column.getColumnName() + " is nullable");
834        }
835100       else if (type == TokenType.UNIQUE)
836        {
837100          column.setUnique(true);
838100          logger.finer("Parsed inline constraint " + constraintName + ": "
839                 + column.getColumnName() + " is unique");
840        }
841100       else if (type == TokenType.PRIMARY)
842        {
8430          assertNextToken(TokenType.KEY);
8440          column.setPrimaryKey(true);
8450          logger.finer("Parsed inline constraint " + constraintName + ": "
846                 + column.getColumnName() + " is primary key");
847        }
848100       else if (type == TokenType.CHECK)
849        {
850100          final StringBuffer checkCondition = new StringBuffer();
851100          assertNextToken(TokenType.OPEN_PAREN);
852100          mScanner.setReportWhitespace(true);
853100          checkCondition.append(eatUntilClosingParen());
854100          mScanner.setReportWhitespace(false);
855100          assertNextToken(TokenType.CLOSE_PAREN);
856100          logger.finer("Parsed inline constraint " + constraintName + ": "
857                 + column.getColumnName() + " check condition: "
858                 + checkCondition);
859100       }
8600       else if (type == TokenType.REFERENCES)
861        {
8620          final String refTable
863               = assertNextToken(TokenType.IDENTIFIER).getValue();
8640          String refColumn = null;
865  
8660          token = getNextToken();
8670          if (token.getType() == TokenType.OPEN_PAREN)
868           {
8690             refColumn = assertNextToken(TokenType.IDENTIFIER).getValue();
8700             assertNextToken(TokenType.CLOSE_PAREN);
871           }
872           else
873           {
8740             refColumn = column.getColumnName();
8750             pushToken(token);
876           }
877  
8780          String deleteSemantic = "default";
879  
8800          token = getNextToken();
8810          type = token.getType();
8820          if (type.equals(TokenType.ON))
883           {
8840             assertNextToken(TokenType.DELETE);
8850             token = getNextToken();
8860             type = token.getType();
8870             if (type == TokenType.SET)
888              {
8890                assertNextToken(TokenType.NULL);
8900                deleteSemantic  = "set null";
891              }
892              else
893              {
8940                assertToken(token, TokenType.CASCADE);
8950                deleteSemantic = "cascade";
896              }
897           }
898           else
899           {
900              // oops, no on delete clause
9010             pushToken(token);
902           }
903  
9040          final FkConstraint constraint = new FkConstraint(
905                 constraintName, column.getColumnName(), refTable, refColumn);
9060          stmt.addFkConstraint(constraint);
9070          logger.finer("Parsed inline constraint " + constraintName + ": "
908                 + column.getColumnName() + " references "
909                 + refTable
910                 + (refColumn != null ? "." + refColumn : "")
911                 + " with " + deleteSemantic + " delete semantic");
912        }
913100    }
914  
915     private boolean isStartOfInlineConstraint (Token token)
916     {
917100       final TokenType type = token.getType();
918100       return type == TokenType.NOT
919              || type == TokenType.NULL
920              || type == TokenType.UNIQUE
921              || type == TokenType.PRIMARY
922              || type == TokenType.CHECK
923              || type == TokenType.REFERENCES
924              || type == TokenType.CONSTRAINT;
925     }
926  
927     private boolean isStartOfOutOfLineConstraint (Token token)
928     {
929100       final TokenType type = token.getType();
930100       return type == TokenType.CONSTRAINT
931              || type == TokenType.UNIQUE
932              || type == TokenType.PRIMARY
933              || type == TokenType.FOREIGN
934              || type == TokenType.CHECK;
935     }
936  
937     private StringBuffer eatUntilClosingParen ()
938        throws ParseException
939     {
940100       final StringBuffer sbuf = new StringBuffer();
941  
942        Token token;
943100(19)      while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
944        {
945100          sbuf.append(token.getValue());
946100          if (token.getType() == TokenType.OPEN_PAREN)
947           {
948100             final StringBuffer nested = eatUntilClosingParen();
949100             sbuf.append(nested);
950100             token = assertNextToken(TokenType.CLOSE_PAREN);
951100             sbuf.append(token.getValue());
952           }
953100          logger.finest("Skipping token " + token);
954        }
955100       pushToken(token);
956  
957100       return sbuf;
958     }
959  
960     private Token eatUntilSemicolon ()
961        throws ParseException
962     {
963        Token token;
964        while (true)
965        {
966100          token = getNextToken();
967100          if (token.getType() == TokenType.SEMICOLON)
968           {
969100             break;
970           }
971100          logger.fine("Skipping token " + token);
972        }
973100       return token;
974     }
975  
976     private void parseOutOfLineConstraint (CreateTableStatement statement)
977        throws ParseException
978     {
979100       String constraintName = "unnamed";
980  
981        Token token;
982        TokenType type;
983  
984        // 1. parse "CONSTRAINT <name>"
985100       token = getNextToken();
986100       type = token.getType();
987100       if (type == TokenType.CONSTRAINT)
988        {
989100          token = assertNextToken(TokenType.IDENTIFIER);
990100          constraintName = token.getValue();
991        }
992        else
993        {
9940          pushToken(token);
995        }
996  
997        // 2. parse the constraint itself
998100       token = getNextToken();
999100       type = token.getType();
1000  
1001100       if (type == TokenType.UNIQUE)
1002        {
1003           // UNIQUE ( column [, column...] )
10040          assertNextToken(TokenType.OPEN_PAREN);
10050          final List uniqueCols = new ArrayList();
10060(20)         while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
1007           {
10080             if (token.getType() != TokenType.COMMA)
1009              {
10100                uniqueCols.add(token.getValue());
1011              }
1012           }
10130(21)         logger.finer("Parsed OOL constraint "
1014                 + constraintName + ": UNIQUE " + uniqueCols);
10150          addUniqueColumns(statement, uniqueCols);
10160       }
1017100       else if (type == TokenType.PRIMARY)
1018        {
1019           // PRIMARY KEY ( column [, column...] )
1020100          assertNextToken(TokenType.KEY);
1021100          assertNextToken(TokenType.OPEN_PAREN);
1022100          final List pkCols = new ArrayList();
1023100(22)         while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
1024           {
1025100             if (token.getType() != TokenType.COMMA)
1026              {
1027100                pkCols.add(token.getValue());
1028              }
1029           }
1030100          logger.finer("Parsed OOL constraint "
1031                 + constraintName + ": PRIMARY KEY " + pkCols);
1032100          addPkColumns(statement, pkCols);
1033100       }
1034100       else if (type == TokenType.FOREIGN)
1035        {
10360          parseOolForeignKey(constraintName, statement);
1037        }
1038100       else if (type == TokenType.CHECK)
1039        {
1040100          token = assertNextToken(TokenType.OPEN_PAREN);
1041100          final StringBuffer checkCondition = new StringBuffer();
1042100          checkCondition.append(token.getValue());
1043100          mScanner.setReportWhitespace(true);
1044100          checkCondition.append(eatUntilClosingParen());
1045100          mScanner.setReportWhitespace(false);
1046100          token = assertNextToken(TokenType.CLOSE_PAREN);
1047100          checkCondition.append(token.getValue());
1048100          logger.finer("Parsed OOL constraint "
1049              + constraintName + ": CHECK " + checkCondition);
1050        }
1051  
1052100       parseConstraintState();
1053  
1054100       token = getNextToken();
1055100       if (token.getType() != TokenType.COMMA)
1056        {
1057100          pushToken(token);
1058        }
1059100    }
1060  
1061     /**
1062      * Method to parse an oracle constraint state.
1063      *
1064      * This actually just parses a subset of an constraint state,
1065      * namely the ENABLE/DISABLE state.
1066      *
1067      * @throws ParseException
1068    */
1069     private void parseConstraintState ()
1070           throws ParseException
1071     {
1072100       final Token token = getNextToken();
1073100       final TokenType type = token.getType();
1074100       if (type == TokenType.ENABLE
1075              || type == TokenType.DISABLE)
1076        {
1077100          logger.finer("Parsed constraint state: " + token);
1078        }
107966       else if (type == TokenType.COMMA
1080              || type == TokenType.CLOSE_PAREN
1081              || type == TokenType.COMMENT)
1082        {
1083           // end of constraint reached
1084100          pushToken(token);
1085        }
1086        else
1087        {
10880          unexpectedToken(token);
1089        }
1090100    }
1091  
1092 (23)(24)(25)   private void parseOolForeignKey (String constraintName,
1093           CreateTableStatement stmt)
1094           throws ParseException
1095     {
1096        Token token;
1097        TokenType type;
1098  
1099        // FOREIGN KEY "(" column [, column...] ")"
1100        // REFERENCES schema.object [ "(" column [, column...] ")" ]
1101        // [ ON DELETE ( CASCADE | SET NULL ) ]
1102        // [ (ENABLE | DISABLE) ]
11030       assertNextToken(TokenType.KEY);
11040       assertNextToken(TokenType.OPEN_PAREN);
11050       final List fkCols = new ArrayList();
11060(26)      while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
1107        {
11080          if (token.getType() != TokenType.COMMA)
1109           {
11100             fkCols.add(token.getValue());
1111           }
1112        }
11130       assertNextToken(TokenType.REFERENCES);
11140       token = assertNextToken(TokenType.IDENTIFIER);
11150(27)      final String refTable = token.getValue();
11160       final List refColumns = new ArrayList();
11170       token = getNextToken();
11180       if (token.getType() == TokenType.OPEN_PAREN)
1119        {
1120           while (true)
1121           {
11220             token = assertNextToken(TokenType.IDENTIFIER);
11230             refColumns.add(token.getValue());
1124  
11250             token = getNextToken();
1126  
11270             if (token.getType() == TokenType.CLOSE_PAREN)
1128              {
11290                break;
1130              }
11310             else if (token.getType() == TokenType.COMMA)
1132              {
1133                 // expected
11340                continue;
1135              }
1136              else
1137              {
11380                unexpectedToken(token);
1139              }
1140           }
1141        }
1142        else
1143        {
11440          refColumns.addAll(fkCols);
11450          pushToken(token);
1146        }
1147  
11480       token = getNextToken();
11490       type = token.getType();
11500       String deleteSemantic = "default";
11510       if (type == TokenType.ON)
1152        {
11530          assertNextToken(TokenType.DELETE);
11540          token = getNextToken();
11550          if (token.getType() == TokenType.SET)
1156           {
11570             assertNextToken(TokenType.NULL);
11580             deleteSemantic = "set null";
1159           }
11600          else if (token.getType() == TokenType.CASCADE)
1161           {
11620             deleteSemantic = "cascade";
1163           }
1164           else
1165           {
11660             unexpectedToken(token);
1167           }
1168        }
1169        else
1170        {
11710          pushToken(token);
1172        }
1173  
1174        // constraint state
11750       String constraintState = null;
11760       token = getNextToken();
11770       type = token.getType();
11780       if (type == TokenType.ENABLE
1179              || type == TokenType.DISABLE)
1180        {
11810          constraintState = token.getValue();
1182        }
1183        else
1184        {
11850          pushToken(token);
1186        }
1187  
11880       token = getNextToken();
11890       type = token.getType();
11900       if (type == TokenType.COMMA
1191              || type == TokenType.CLOSE_PAREN
1192              || type == TokenType.COMMENT)
1193        {
1194           // uff, we're done
11950          pushToken(token);
1196  
11970          final FkConstraint constraint = new FkConstraint(
1198                 constraintName, fkCols, refTable, refColumns);
11990          stmt.addFkConstraint(constraint);
12000          logger.finer("Parsed OOL constraint "
1201                 + constraintName + ": FOREIGN KEY: " + fkCols
1202                 + " reference " + refTable
1203                 + "(" + refColumns + ")"
1204                 + " with " + deleteSemantic + " delete semantic"
1205                 + (constraintState != null
1206                    ? (" and constraint state " + constraintState)
1207                    : ""));
12080       }
1209        else
1210        {
12110          unexpectedToken(token);
1212        }
12130    }
1214  
1215     private void addUniqueColumns (CreateTableStatement statement, List ukCols)
1216           throws ParseException
1217     {
12180       for (final Iterator it = ukCols.iterator(); it.hasNext(); )
1219        {
12200          final String colName = (String) it.next();
12210          final ColumnSpec col = statement.getColumnByName(colName);
12220          if (col == null)
1223           {
12240             throw new ParseException("Column " + colName + " not found",
1225                    mScanner.getLine(), mScanner.getColumn());
1226           }
12270          col.setUnique(true);
12280       }
12290    }
1230  
1231     /**
1232      * convenience method to add a set of columns as PK columns to
1233      * a create statement.
1234      *
1235      * @param pkCols list of column names (Strings) that should be PK
1236      */
1237     private void addPkColumns (CreateTableStatement statement, List pkCols)
1238           throws ParseException
1239     {
1240        // first check that no primary key exists
1241100       checkForPrimaryKey(statement);
1242  
1243100       for (final Iterator it = pkCols.iterator(); it.hasNext(); )
1244        {
1245100          final String colName = (String) it.next();
1246100          final ColumnSpec col = statement.getColumnByName(colName);
1247100          if (col == null)
1248           {
12490             throw new ParseException("Column " + colName + " not found",
1250                    mScanner.getLine(), mScanner.getColumn());
1251           }
1252100          col.setPrimaryKey(true);
1253100       }
1254100    }
1255  
1256     /**
1257      * checks if the statement already has a column with PK
1258      *
1259      * @param statement a SQL create table statement
1260      * @throws ParseException if a primary key exists in the statement
1261      */
1262     private void checkForPrimaryKey (CreateTableStatement statement)
1263        throws ParseException
1264     {
1265100       for (final Iterator it = statement.getColumns().iterator();
1266100           it.hasNext(); )
1267        {
1268100          final ColumnSpec col = (ColumnSpec) it.next();
1269100          if (col.isPrimaryKey())
1270           {
12710             throw new ParseException(col.getColumnName()
1272                    + " is already primary key",
1273                    mScanner.getLine(), mScanner.getColumn());
1274           }
1275100       }
1276100    }
1277  
1278     /**
1279      * assert that the next token is of the expected type return next token
1280      * @param expectedType
1281    * @throws ParseException
1282    */
1283     private Token assertNextToken (TokenType expectedType)
1284        throws ParseException
1285     {
1286        // final String methodName = "assertNextToken(TokenType)";
1287        // logger.entering(CLASSNAME, methodName, expectedType);
1288100       final Token nextToken = getNextToken();
1289100       if (nextToken.getType() != expectedType)
1290        {
12910          throw new ParseException(
1292              "Unexpected token: expected "
1293                 + expectedType
1294                 + " but got "
1295                 + nextToken.getType(),
1296              mScanner.getLine(),
1297              mScanner.getColumn());
1298        }
1299  
1300        // logger.exiting(CLASSNAME, methodName, nextToken);
1301100       return nextToken;
1302     }
1303  
1304     private void unexpectedToken (Token token) throws ParseException
1305     {
13060       throw new ParseException(
1307              "Unexpected token: " + token,
1308              mScanner.getLine(),
1309              mScanner.getColumn());
1310     }
1311  
1312     private void assertToken (Token token, TokenType expectedType)
1313           throws ParseException
1314     {
13150       if (token.getType() != expectedType)
1316        {
13170          throw new ParseException(
1318              "Unexpected token: expected "
1319                 + expectedType
1320                 + " but got "
1321                 + token.getType(),
1322              mScanner.getLine(),
1323              mScanner.getColumn());
1324        }
13250    }
1326  
1327     /**
1328      * Main method is just to test parser.
1329      *
1330      * @param args command line arguments
1331      * @throws Exception if something goes wrong
1332      */
1333     public static void main (String[] args)
1334 (28)      throws Exception
1335     {
13360       final FileInputStream fin = new FileInputStream(args[0]);
13370       final SqlScanner scanner = new SqlScanner(fin);
13380       final SqlParser parser = new SqlParser(scanner);
13390       parser.parse();
13400    }
1341  }

Findings in this File

c (1) 199 : 21 Inner assignments should be avoided.
w (2) 204 : 0 Method org.jcoderz.phoenix.sqlparser.SqlParser.parse() uses instanceof on multiple types to arbitrate logic
i (3) 278 : 0 method org.jcoderz.phoenix.sqlparser.SqlParser.handleStartOfStatement(Token) throws exception with static message string
c (4) 286 : 4 Cyclomatic Complexity is 20 (max allowed is 12).
d (5) 286 : 4 Method length is 140 lines (max allowed is 100).
d (6) 341 : 14 Substitute calls to size() == 0 (or size() != 0) with calls to isEmpty()
c (7) 429 : 4 Cyclomatic Complexity is 14 (max allowed is 12).
c (8) 433 : 21 Inner assignments should be avoided.
w (9) 446 : 0 method org.jcoderz.phoenix.sqlparser.SqlParser.parseCreateSequenceStatement(CreateSequenceStatement) passes parsed string to primitive wrapper constructor
w (10) 451 : 0 method org.jcoderz.phoenix.sqlparser.SqlParser.parseCreateSequenceStatement(CreateSequenceStatement) passes parsed string to primitive wrapper constructor
w (11) 460 : 0 method org.jcoderz.phoenix.sqlparser.SqlParser.parseCreateSequenceStatement(CreateSequenceStatement) passes parsed string to primitive wrapper constructor
c (12) 561 : 4 Cyclomatic Complexity is 13 (max allowed is 12).
c (13) 561 : 12 The method parseColumnSpec() has an NPath complexity of 200
i (14) 574 : 0 method org.jcoderz.phoenix.sqlparser.SqlParser.parseColumnSpec(CreateTableStatement) makes literal string comparisons passing the literal as an argument
i (15) 579 : 0 method org.jcoderz.phoenix.sqlparser.SqlParser.parseColumnSpec(CreateTableStatement) makes literal string comparisons passing the literal as an argument
c (16) 718 : 4 Cyclomatic Complexity is 14 (max allowed is 12).
d (17) 799 : 4 Method length is 112 lines (max allowed is 100).
i (18) 826 : 23 The String literal "Parsed inline constraint " appears 6 times in this file; the first occurrence is on line 826
c (19) 943 : 21 Inner assignments should be avoided.
c (20) 1006 : 24 Inner assignments should be avoided.
i (21) 1013 : 23 The String literal "Parsed OOL constraint " appears 4 times in this file; the first occurrence is on line 1,013
c (22) 1023 : 24 Inner assignments should be avoided.
c (23) 1092 : 4 Cyclomatic Complexity is 16 (max allowed is 12).
d (24) 1092 : 4 Method length is 119 lines (max allowed is 100).
c (25) 1092 : 12 The method parseOolForeignKey() has an NPath complexity of 1440
c (26) 1106 : 21 Inner assignments should be avoided.
w (27) 1115 : 0 Method org.jcoderz.phoenix.sqlparser.SqlParser.parseOolForeignKey(String, CreateTableStatement) assigns a variable in a larger scope then is needed
d (28) 1334 : 14 A method/constructor shouldn't explicitly throw java.lang.Exception