/*
* 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.example.ddl;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_LENGTH;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_NAME;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_PRECISION;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_SCALE;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DDL_EXPRESSION;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DEFAULT_VALUE;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_ADD_COLUMN_DEFINITION;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_ALTER_COLUMN_DEFINITION;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_ALTER_TABLE_STATEMENT;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_COLUMN_DEFINITION;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_CREATE_SCHEMA_STATEMENT;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_CREATE_TABLE_STATEMENT;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_CREATE_VIEW_STATEMENT;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_DROP_SCHEMA_STATEMENT;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_DROP_TABLE_STATEMENT;
import static org.modeshape.sequencer.ddl.StandardDdlLexicon.TYPE_DROP_VIEW_STATEMENT;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.LinkedHashMap;
import java.util.Map;
import org.modeshape.sequencer.ddl.DdlParsers;
import org.modeshape.sequencer.ddl.StandardDdlLexicon;
import org.modeshape.sequencer.ddl.node.AstNode;
/**
* A simple example application that demonstrates how to parse DDL files to create an Abstract Syntax Tree (AST) as a ModeShape
* graph and to then post-processes that graph.
* <p>
* The {@link #parse(File)} method returns a {@link Database} object that contains a final representation of the structure of an
* empty database after the supplied (simple) schema is applied. After all, DDL statements are actually operations against a
* database, so a DDL file with some statements (like "DROP TABLE foo") don't make sense when applied to an empty database.
* </p>
* <p>
* However, this example works really well when processing DDL statements that create tables, views, and schemas.
* </p>
* <p>
* Note: this example does not process all of the available DDL statements, so many statements (e.g., foreign keys, primary keys,
* etc.) are ignored. Support for these are left to the reader. The actual structure of the files is described by the following
* JCR CND node type definition files contained within the "modeshape-sequencer-ddl" library:
* <ul>
* <li>org.modeshape.sequencer.ddl.StandardDdl.cnd</li>
* <li>org.modeshape.sequencer.ddl.dialect.derby.DerbyDdl.cnd</li>
* <li>org.modeshape.sequencer.ddl.dialect.oracle.OracleDdl.cnd</li>
* <li>org.modeshape.sequencer.ddl.dialect.postgres.PostgresDdl.cnd</li>
* </ul>
* </p>
*/
public class Parser {
public static void main( String[] argv ) {
// Process the arguments ...
String filename = null;
for (String arg : argv) {
// First non-flag parameter is the filename ...
if (filename == null) filename = arg;
}
// Figure out if the arguments are valid ...
if (filename == null) {
printUsage(System.out);
System.exit(1);
}
File file = new File(filename);
if (!file.exists()) printError(-1, "The file \"" + filename + "\" does not exist.");
if (!file.isFile()) printError(-2, "File could not be found at \"" + file + "\"");
if (!file.canRead()) printError(-3, "Unable to read file \"" + filename + "\".");
// Now parse the file ...
try {
Parser parser = new Parser();
parser.parse(file);
} catch (Throwable t) {
printError(-10, t.getMessage());
}
}
protected static void printError( int exitCode,
String message ) {
System.err.println("Error: " + message);
System.err.println();
printUsage(System.out);
System.exit(exitCode);
}
protected static void printUsage( PrintStream stream ) {
stream.println("Usage: " + Parser.class.getCanonicalName() + " filename");
stream.println();
stream.println(" where");
stream.println(" filename is the name of the DDL file to be parsed");
stream.println();
}
protected static String readFile( File file ) throws IOException {
StringBuffer fileData = new StringBuffer(1000);
BufferedReader reader = new BufferedReader(new FileReader(file));
try {
char[] buf = new char[1024];
int numRead = 0;
while ((numRead = reader.read(buf)) != -1) {
String readData = String.valueOf(buf, 0, numRead);
fileData.append(readData);
buf = new char[1024];
}
return fileData.toString();
} finally {
reader.close();
}
}
protected boolean print = true;
public Database parse( File file ) throws IOException {
// Read the file into a single string ...
String ddl = readFile(file);
// Create the object that will parse the file ...
DdlParsers parsers = new DdlParsers();
AstNode node = parsers.parse(ddl, file.getName());
// Now process the AST ...
System.out.println(node.toString());
return processStatements(node);
}
/**
* Process the top-level 'ddl:statements' node, which contains information about the parsed content.
*
* @param node the node; may not be null
* @return the database
*/
protected Database processStatements( AstNode node ) {
assert node.getName().equals(StandardDdlLexicon.STATEMENTS_CONTAINER);
// Get the dialect that we've inferred ...
String dialectId = string(node.getProperty(StandardDdlLexicon.PARSER_ID));
// Now process the children of the statements node ...
Database database = new Database(dialectId);
processChildrenOf(node, database);
return database;
}
protected void processChildrenOf( AstNode node,
NamedComponent parent ) {
for (AstNode child : node) {
process(child, parent);
}
}
protected void process( AstNode node,
NamedComponent parent ) {
String mixin = string(node.getProperty("jcr:mixinTypes"));
// There are lots of different types of AST nodes, but we're only going to process a few ...
if (TYPE_CREATE_SCHEMA_STATEMENT.equals(mixin)) processCreateSchema(node, (SchemaContainer)parent);
else if (TYPE_DROP_SCHEMA_STATEMENT.equals(mixin)) processDropSchema(node, (SchemaContainer)parent);
else if (TYPE_CREATE_TABLE_STATEMENT.equals(mixin)) processCreateTable(node, (TableContainer)parent);
else if (TYPE_ALTER_TABLE_STATEMENT.equals(mixin)) processAlterTable(node, (TableContainer)parent);
else if (TYPE_DROP_TABLE_STATEMENT.equals(mixin)) processDropTable(node, (TableContainer)parent);
else if (TYPE_CREATE_VIEW_STATEMENT.equals(mixin)) processCreateView(node, (ViewContainer)parent);
else if (TYPE_DROP_VIEW_STATEMENT.equals(mixin)) processDropView(node, (ViewContainer)parent);
else if (TYPE_COLUMN_DEFINITION.equals(mixin)) processColumnDefinition(node, (ColumnContainer)parent);
else if (TYPE_ADD_COLUMN_DEFINITION.equals(mixin)) processColumnDefinition(node, (ColumnContainer)parent);
else if (TYPE_ALTER_COLUMN_DEFINITION.equals(mixin)) processColumnDefinition(node, (ColumnContainer)parent);
}
protected void processCreateSchema( AstNode node,
SchemaContainer parent ) {
String name = string(node.getName());
print("Create schema \"" + name + "\"");
Schema schema = parent.getSchema(name, true);
processChildrenOf(node, schema);
}
protected void processDropSchema( AstNode node,
SchemaContainer parent ) {
String name = string(node.getName());
print("Drop schema \"" + name + "\"");
parent.getSchemas().remove(name);
}
protected void processCreateTable( AstNode node,
TableContainer parent ) {
String name = string(node.getName());
print("Create table \"" + name + "\"");
Table table = parent.getTable(name, true);
processChildrenOf(node, table);
}
protected void processAlterTable( AstNode node,
TableContainer parent ) {
String name = string(node.getName());
print("Alter table \"" + name + "\"");
Table table = parent.getTable(name, true);
processChildrenOf(node, table);
}
protected void processDropTable( AstNode node,
TableContainer parent ) {
String name = string(node.getName());
print("Drop table \"" + name + "\"");
parent.getTables().remove(name);
}
protected void processCreateView( AstNode node,
ViewContainer parent ) {
String name = string(node.getName());
print("Create view \"" + name + "\"");
View view = parent.getView(name, true);
Object prop = node.getProperty(DDL_EXPRESSION);
if (prop != null) view.expression = string(prop);
processChildrenOf(node, view);
}
protected void processDropView( AstNode node,
ViewContainer parent ) {
String name = string(node.getName());
print("Drop view \"" + name + "\"");
parent.getViews().remove(name);
}
protected void processColumnDefinition( AstNode node,
ColumnContainer parent ) {
String name = string(node.getName());
print("Column column \"" + name + "\"");
Column column = parent.getColumn(name, true);
Object prop = node.getProperty(DATATYPE_LENGTH);
if (prop != null) column.length = (int)number(prop);
prop = node.getProperty(DATATYPE_NAME);
if (prop != null) column.datatypeName = string(prop);
prop = node.getProperty(DATATYPE_PRECISION);
if (prop != null) column.precision = (int)number(prop);
prop = node.getProperty(DATATYPE_SCALE);
if (prop != null) column.scale = (int)number(prop);
prop = node.getProperty(DEFAULT_VALUE);
if (prop != null) column.defaultValue = string(prop);
}
/**
* Convenience method to transform a property value into a string representation. Property values are often able to be
* tranformed into multiple types, so the ModeShape Graph API is designed so that you always convert the values into the
* desired type.
*
* @param value the property value; may be null
* @return the string representation of the value, or null if the value was null
*/
protected String string( Object value ) {
return value == null ? null : value.toString();
}
protected long number( Object value ) {
return value == null ? null : (Long)value;
}
protected void print( String message ) {
if (print) System.out.println(message);
}
public static abstract class NamedComponent {
protected final String name;
protected NamedComponent( String name ) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public static class Table extends NamedComponent implements ColumnContainer {
protected final Map<String, Column> columns = new LinkedHashMap<String, Column>();
public Table( String name ) {
super(name);
}
@Override
public Column getColumn( String name,
boolean createIfMissing ) {
Column column = columns.get(name);
if (column == null && createIfMissing) {
column = new Column(name);
columns.put(name, column);
}
return column;
}
@Override
public Map<String, Column> getColumns() {
return columns;
}
}
public static class View extends NamedComponent {
protected String expression;
public View( String name ) {
super(name);
}
}
public static class Column extends NamedComponent {
protected int length;
protected String datatypeName;
protected int precision;
protected int scale;
protected String defaultValue;
public Column( String name ) {
super(name);
}
}
public static interface SchemaContainer {
Schema getSchema( String name,
boolean createIfMissing );
Map<String, Schema> getSchemas();
}
public static interface TableContainer {
Table getTable( String name,
boolean createIfMissing );
Map<String, Table> getTables();
}
public static interface ViewContainer {
View getView( String name,
boolean createIfMissing );
Map<String, View> getViews();
}
public static interface ColumnContainer {
Column getColumn( String name,
boolean createIfMissing );
Map<String, Column> getColumns();
}
public static class Schema extends NamedComponent implements TableContainer, ViewContainer {
protected final Map<String, Table> tables = new LinkedHashMap<String, Table>();
protected final Map<String, View> views = new LinkedHashMap<String, View>();
public Schema( String name ) {
super(name);
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.TableContainer#getTable(java.lang.String, boolean)
*/
@Override
public Table getTable( String name,
boolean createIfMissing ) {
Table table = tables.get(name);
if (table == null && createIfMissing) {
table = new Table(name);
tables.put(name, table);
}
return table;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.TableContainer#getTables()
*/
@Override
public Map<String, Table> getTables() {
return tables;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.ViewContainer#getView(java.lang.String, boolean)
*/
@Override
public View getView( String name,
boolean createIfMissing ) {
View view = views.get(name);
if (view == null && createIfMissing) {
view = new View(name);
views.put(name, view);
}
return view;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.ViewContainer#getViews()
*/
@Override
public Map<String, View> getViews() {
return views;
}
}
public static class Database extends NamedComponent implements TableContainer, ViewContainer, SchemaContainer {
protected final Map<String, Table> tables = new LinkedHashMap<String, Table>();
protected final Map<String, View> views = new LinkedHashMap<String, View>();
protected final Map<String, Schema> schemas = new LinkedHashMap<String, Schema>();
public Database( String name ) {
super(name);
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.TableContainer#getTable(java.lang.String, boolean)
*/
@Override
public Table getTable( String name,
boolean createIfMissing ) {
Table table = tables.get(name);
if (table == null && createIfMissing) {
table = new Table(name);
tables.put(name, table);
}
return table;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.TableContainer#getTables()
*/
@Override
public Map<String, Table> getTables() {
return tables;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.ViewContainer#getView(java.lang.String, boolean)
*/
@Override
public View getView( String name,
boolean createIfMissing ) {
View view = views.get(name);
if (view == null && createIfMissing) {
view = new View(name);
views.put(name, view);
}
return view;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.ViewContainer#getViews()
*/
@Override
public Map<String, View> getViews() {
return views;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.SchemaContainer#getSchema(java.lang.String, boolean)
*/
@Override
public Schema getSchema( String name,
boolean createIfMissing ) {
Schema schema = schemas.get(name);
if (schema == null && createIfMissing) {
schema = new Schema(name);
schemas.put(name, schema);
}
return schema;
}
/**
* {@inheritDoc}
*
* @see org.modeshape.example.ddl.Parser.SchemaContainer#getSchemas()
*/
@Override
public Map<String, Schema> getSchemas() {
return schemas;
}
}
}