All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.sequencer.ddl.DdlParsers Maven / Gradle / Ivy

The newest version!
/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.modeshape.sequencer.ddl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.text.ParsingException;
import org.modeshape.common.text.Position;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.api.JcrConstants;
import org.modeshape.sequencer.ddl.dialect.derby.DerbyDdlParser;
import org.modeshape.sequencer.ddl.dialect.oracle.OracleDdlParser;
import org.modeshape.sequencer.ddl.dialect.postgres.PostgresDdlParser;
import org.modeshape.sequencer.ddl.node.AstNode;
import org.modeshape.sequencer.ddl.node.AstNodeFactory;

/**
 * A set of parsers capable of understanding DDL file content. This class can be used directly to create an {@link AstNode} tree
 * representing nodes and properties for DDL statement components.
 * 

* You can also provide an input or parent {@link AstNode} node as the starting point for your tree. *

*

* The parser is based on the SQL-92 and extended by specific dialects. These dialect-specific parsers provide db-specific parsing * of db-specific statements of statement extensions, features or properties. *

*/ @Immutable public class DdlParsers { /** * Sorts the parser scores. */ private static final Comparator> SORTER = new Comparator>() { @Override public int compare( final Entry thisEntry, final Entry thatEntry ) { // reverse order as we want biggest value to sort first int result = (thisEntry.getValue().compareTo(thatEntry.getValue()) * -1); // default to standard SQL parser if score is a tie if (result == 0) { if (StandardDdlParser.ID.equals(thisEntry.getKey().getId()) && !StandardDdlParser.ID.equals(thatEntry.getKey().getId())) { return -1; } if (StandardDdlParser.ID.equals(thatEntry.getKey().getId()) && !StandardDdlParser.ID.equals(thisEntry.getKey().getId())) { return 1; } } return result; } }; public static final List BUILTIN_PARSERS; static { List parsers = new ArrayList(); parsers.add(new StandardDdlParser()); parsers.add(new OracleDdlParser()); parsers.add(new DerbyDdlParser()); parsers.add(new PostgresDdlParser()); BUILTIN_PARSERS = Collections.unmodifiableList(parsers); } private List parsers; private AstNodeFactory nodeFactory = new AstNodeFactory(); /** * Create an instance that uses all of the {@link #BUILTIN_PARSERS built-in parsers}. */ public DdlParsers() { this.parsers = BUILTIN_PARSERS; } /** * Create an instance that uses the supplied parsers, in order. * * @param parsers the list of parsers; may be empty or null if the {@link #BUILTIN_PARSERS built-in parsers} should be used */ public DdlParsers( List parsers ) { this.parsers = (parsers != null && !parsers.isEmpty()) ? parsers : BUILTIN_PARSERS; } private AstNode createDdlStatementsContainer( final String parserId ) { final AstNode node = this.nodeFactory.node(StandardDdlLexicon.STATEMENTS_CONTAINER); node.setProperty(JcrConstants.JCR_PRIMARY_TYPE, JcrConstants.NT_UNSTRUCTURED); node.setProperty(StandardDdlLexicon.PARSER_ID, parserId); return node; } /** * @param id the identifier of the parser being requested (cannot be null or empty) * @return the parser or null if not found */ public DdlParser getParser( final String id ) { CheckArg.isNotEmpty(id, "id"); for (final DdlParser parser : this.parsers) { if (parser.getId().equals(id)) { return parser; } } return null; } /** * @return a copy of the DDL parsers used in this instance (never null or empty) */ public Set getParsers() { return new HashSet(this.parsers); } /** * @param ddl the DDL being parsed (cannot be null or empty) * @param parserId the identifier of the parser to use (can be null or empty if best matched parser should be * used) * @return the root tree {@link AstNode} * @throws ParsingException if there is an error parsing the supplied DDL content * @throws IllegalArgumentException if a parser with the specified identifier cannot be found */ public AstNode parseUsing( final String ddl, final String parserId ) throws ParsingException { CheckArg.isNotEmpty(ddl, "ddl"); CheckArg.isNotEmpty(parserId, "parserId"); DdlParser parser = getParser(parserId); if (parser == null) { throw new ParsingException(Position.EMPTY_CONTENT_POSITION, DdlSequencerI18n.unknownParser.text(parserId)); } // create DDL root node AstNode astRoot = createDdlStatementsContainer(parserId); // parse parser.parse(ddl, astRoot, null); return astRoot; } /** * Parse the supplied DDL using multiple parsers, returning the result of each parser with its score in the order of highest * scoring to lowest scoring. * * @param ddl the DDL being parsed (cannot be null or empty) * @param firstParserId the identifier of the first parser to use (cannot be null or empty) * @param secondParserId the identifier of the second parser to use (cannot be null or empty) * @param additionalParserIds the identifiers of additional parsers that should be used; may be empty but not contain a null * identifier value * @return the list of {@link ParsingResult} instances, one for each parser, ordered from highest score to lowest score (never * null or empty) * @throws ParsingException if there is an error parsing the supplied DDL content * @throws IllegalArgumentException if a parser with the specified identifier cannot be found */ public List parseUsing( final String ddl, final String firstParserId, final String secondParserId, final String... additionalParserIds ) throws ParsingException { CheckArg.isNotEmpty(firstParserId, "firstParserId"); CheckArg.isNotEmpty(secondParserId, "secondParserId"); if (additionalParserIds != null) { CheckArg.containsNoNulls(additionalParserIds, "additionalParserIds"); } final int numParsers = ((additionalParserIds == null) ? 2 : (additionalParserIds.length + 2)); final List selectedParsers = new ArrayList(numParsers); { // add first parser final DdlParser parser = getParser(firstParserId); if (parser == null) { throw new ParsingException(Position.EMPTY_CONTENT_POSITION, DdlSequencerI18n.unknownParser.text(firstParserId)); } selectedParsers.add(parser); } { // add second parser final DdlParser parser = getParser(secondParserId); if (parser == null) { throw new ParsingException(Position.EMPTY_CONTENT_POSITION, DdlSequencerI18n.unknownParser.text(secondParserId)); } selectedParsers.add(parser); } // add remaining parsers if ((additionalParserIds != null) && (additionalParserIds.length != 0)) { for (final String id : additionalParserIds) { final DdlParser parser = getParser(id); if (parser == null) { throw new ParsingException(Position.EMPTY_CONTENT_POSITION, DdlSequencerI18n.unknownParser.text(id)); } selectedParsers.add(parser); } } return parseUsing(ddl, selectedParsers); } private List parseUsing( final String ddl, final List parsers ) { CheckArg.isNotEmpty(ddl, "ddl"); final List results = new ArrayList(this.parsers.size()); final DdlParserScorer scorer = new DdlParserScorer(); for (final DdlParser parser : this.parsers) { final String parserId = parser.getId(); int score = ParsingResult.NO_SCORE; AstNode rootNode = null; Exception error = null; try { // score final Object scorerOutput = parser.score(ddl, null, scorer); score = scorer.getScore(); // create DDL root node rootNode = createDdlStatementsContainer(parserId); // parse parser.parse(ddl, rootNode, scorerOutput); } catch (final RuntimeException e) { error = e; } finally { final ParsingResult result = new ParsingResult(parserId, rootNode, score, error); results.add(result); scorer.reset(); } } Collections.sort(results); return results; } /** * Parse the supplied DDL using all registered parsers, returning the result of each parser with its score in the order of * highest scoring to lowest scoring. * * @param ddl the DDL being parsed (cannot be null or empty) * @return the list or {@link ParsingResult} instances, one for each parser, ordered from highest score to lowest score * @throws ParsingException if there is an error parsing the supplied DDL content * @throws IllegalArgumentException if a parser with the specified identifier cannot be found */ public List parseUsingAll( final String ddl ) throws ParsingException { return parseUsing(ddl, this.parsers); } /** * Parse the supplied DDL content and return the {@link AstNode root node} of the AST representation. * * @param ddl content string; may not be null * @param fileName the approximate name of the file containing the DDL content; may be null if this is not known * @return the root tree {@link AstNode} * @throws ParsingException if there is an error parsing the supplied DDL content */ public AstNode parse( final String ddl, final String fileName ) throws ParsingException { CheckArg.isNotEmpty(ddl, "ddl"); RuntimeException firstException = null; // Go through each parser and score the DDL content final Map scoreMap = new HashMap(this.parsers.size()); final DdlParserScorer scorer = new DdlParserScorer(); for (final DdlParser parser : this.parsers) { try { parser.score(ddl, fileName, scorer); scoreMap.put(parser, scorer.getScore()); } catch (RuntimeException e) { if (firstException == null) { firstException = e; } } finally { scorer.reset(); } } if (scoreMap.isEmpty()) { if (firstException == null) { throw new ParsingException(Position.EMPTY_CONTENT_POSITION, DdlSequencerI18n.errorParsingDdlContent.text(this.parsers.size())); } throw firstException; } // sort the scores final List> scoredParsers = new ArrayList>(scoreMap.entrySet()); Collections.sort(scoredParsers, SORTER); firstException = null; AstNode astRoot = null; for (final Entry scoredParser : scoredParsers) { try { final DdlParser parser = scoredParser.getKey(); // create DDL root node astRoot = createDdlStatementsContainer(parser.getId()); // parse parser.parse(ddl, astRoot, null); return astRoot; // successfully parsed } catch (final RuntimeException e) { if (astRoot != null) { astRoot.removeFromParent(); } if (firstException == null) { firstException = e; } } } if (firstException == null) { throw new ParsingException(Position.EMPTY_CONTENT_POSITION, DdlSequencerI18n.errorParsingDdlContent.text()); } throw firstException; } /** * Represents a parsing result of one parser parsing one DDL input. */ @Immutable public class ParsingResult implements Comparable { public static final int NO_SCORE = -1; private final Exception error; private final String id; private final AstNode rootNode; private final int score; /** * @param parserId the parser identifier (cannot be null or empty) * @param rootTreeNode the node at the root of the parse tree (can be null if an error occurred) * @param parserScore the parsing score (can have {@link #NO_SCORE no score} if an error occurred * @param parsingError an error that occurred during parsing (can be null) */ public ParsingResult( final String parserId, final AstNode rootTreeNode, final int parserScore, final Exception parsingError ) { CheckArg.isNotEmpty(parserId, "parserId"); this.id = parserId; this.rootNode = rootTreeNode; this.score = parserScore; this.error = parsingError; } /** * {@inheritDoc} * * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo( final ParsingResult that ) { if ((this == that) || (this.score == that.score)) { return 0; } return ((this.score > that.score) ? -1 : 1); } /** * @return the parsing error (null if no error occurred) */ public Exception getError() { return this.error; } /** * @return the parser identifier (never null or empty) */ public String getParserId() { return this.id; } /** * @return the root AstNode (can be null if a parsing error occurred) */ public AstNode getRootTree() { return this.rootNode; } /** * @return the parsing score */ public int getScore() { return this.score; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy