| Author | Dave Jarvis <email> |
|---|---|
| Date | 2015-03-24 19:36:56 GMT-0700 |
| Commit | f04471c01f3d076d066e0ae5110d30d6e7f82c10 |
| Parent | 02ded80 |
| import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| -import com.whitemagicsoftware.rxm.tree.xml.SelectClause; | ||
| -import com.whitemagicsoftware.rxm.tree.xml.FromClause; | ||
| -import com.whitemagicsoftware.rxm.tree.xml.JoinClause; | ||
| -import com.whitemagicsoftware.rxm.tree.xml.WhereClause; | ||
| +import com.whitemagicsoftware.rxm.xml.SelectClause; | ||
| +import com.whitemagicsoftware.rxm.xml.FromClause; | ||
| +import com.whitemagicsoftware.rxm.xml.JoinClause; | ||
| +import com.whitemagicsoftware.rxm.xml.WhereClause; | ||
| import org.antlr.v4.runtime.ANTLRInputStream; |
| -package com.whitemagicsoftware.rxm.tree; | ||
| - | ||
| -import org.antlr.v4.runtime.CommonToken; | ||
| -import static org.antlr.v4.runtime.Token.MIN_USER_TOKEN_TYPE; | ||
| - | ||
| -/** | ||
| - * Avoids having to duplicate common token functionality across the different | ||
| - * parser rule contexts. | ||
| - */ | ||
| -public class Token extends CommonToken { | ||
| - public Token( String text ) { | ||
| - super( MIN_USER_TOKEN_TYPE, text ); | ||
| - } | ||
| - | ||
| - public String toString() { | ||
| - return getText(); | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| -import com.whitemagicsoftware.rxm.tree.Token; | ||
| -import com.whitemagicsoftware.rxm.tree.Tree; | ||
| - | ||
| -import org.antlr.v4.runtime.ParserRuleContext; | ||
| - | ||
| -/** | ||
| - * Transforms <b><code>table > @attribute</code></b> into an | ||
| - * <code>XMLATTRIBUTES</code> expression. | ||
| - */ | ||
| -public class AttributeMapContext extends Context { | ||
| - private boolean hasPrecedingPayload = false; | ||
| - private boolean hasFollowingPayload = false; | ||
| - | ||
| - public AttributeMapContext( ParserRuleContext ctx ) { | ||
| - super( ctx ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Opens the XMLATTRIBUTES expression. | ||
| - * | ||
| - * @return Token containing ",XMLATTRIBUTES(" ... | ||
| - */ | ||
| - @Override | ||
| - public Token getStart() { | ||
| - String | ||
| - tableName = getParentTableText(), | ||
| - columnName = getColumnText(), | ||
| - attributeName = getAttributeText(); | ||
| - | ||
| - return new Token( | ||
| - String.format( ",%s", hasPrecedingPayload() ? | ||
| - nextAttribute( tableName, columnName, attributeName ) : | ||
| - firstAttribute( tableName, columnName, attributeName ) | ||
| - ) | ||
| - ); | ||
| - } | ||
| - | ||
| - private boolean hasPrecedingPayload() { | ||
| - return this.hasPrecedingPayload; | ||
| - } | ||
| - | ||
| - private boolean hasFollowingPayload() { | ||
| - return this.hasFollowingPayload; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Closes the XMLATTRIBUTES expression, should no more attributes be | ||
| - * forthcoming. | ||
| - * | ||
| - * @return Token containing ")" or the empty string. | ||
| - */ | ||
| - @Override | ||
| - public Token getStop() { | ||
| - return new Token( hasFollowingPayload() ? "" : ")" ); | ||
| - } | ||
| - | ||
| - private String getAttributeText() { | ||
| - return attribute().getChild(1).getText(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the attribute (node) name. | ||
| - */ | ||
| - private QueryParser.AttributeContext attribute() { | ||
| - return getAttributeMapContext().attribute(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the attribute map context that this class wraps. | ||
| - */ | ||
| - private QueryParser.AttributeMapContext getAttributeMapContext() { | ||
| - return (QueryParser.AttributeMapContext)getParserRuleContext(); | ||
| - } | ||
| - | ||
| - @Override | ||
| - protected QueryParser.ColumnContext column() { | ||
| - return getAttributeMapContext().column(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Formats the start of the XMLATTRIBUTES call. | ||
| - * | ||
| - * @param table The table name assigned to the attribute. | ||
| - * @param column The column name assigned to the attribute. | ||
| - * @param node The document node name assigned to the attribute. | ||
| - * | ||
| - * @return The text used for SQL/XML XMLATTRIBUTES calls. | ||
| - */ | ||
| - private String firstAttribute( String table, String column, String node ) { | ||
| - return "XMLATTRIBUTES(" + nextAttribute( table, column, node ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Formats the next attribute of the XMLATTRIBUTES call. This is only | ||
| - * called when there are contiguous attributes listed in the <b>rxm</b>. | ||
| - * | ||
| - * @param table The table name assigned to the attribute. | ||
| - * @param column The column name assigned to the attribute. | ||
| - * @param node The document node name assigned to the attribute. | ||
| - * | ||
| - * @return The text used for SQL/XML XMLATTRIBUTES calls. | ||
| - */ | ||
| - private String nextAttribute( String table, String column, String node ) { | ||
| - String result = "%s.%s%s"; | ||
| - | ||
| - // If the column name and attribute name are the same, then the | ||
| - // "AS" clause is superfluous. | ||
| - if( column.equals( node ) ) { | ||
| - result = String.format( result, table, column, "" ); | ||
| - } | ||
| - else { | ||
| - node = String.format( " AS \"%s\"", node ); | ||
| - result = String.format( result, table, column, node ); | ||
| - } | ||
| - | ||
| - return result; | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import java.nio.CharBuffer; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| - | ||
| -/** | ||
| - * Common behaviour for various clauses. | ||
| - */ | ||
| -public class Clause extends QueryBaseListener { | ||
| - private static int INDENT = 2; | ||
| - | ||
| - private StringBuilder buffer = new StringBuilder( 2048 ); | ||
| - | ||
| - /** | ||
| - * Default constructor. | ||
| - */ | ||
| - public Clause() { | ||
| - } | ||
| - | ||
| - /** | ||
| - * Appends a given string to the buffer. | ||
| - * | ||
| - * @param s The string to append. | ||
| - * @return this to chain the append calls. | ||
| - */ | ||
| - protected Clause append( String s ) { | ||
| - getBuffer().append( s ); | ||
| - return this; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the buffer converted to a string. This is the result of | ||
| - * the transformed output and should only be called when the parsing | ||
| - * of the <b>rxm</b> source is complete. | ||
| - * | ||
| - * @return A non-null string, possibly empty. | ||
| - */ | ||
| - public String toString() { | ||
| - return getBuffer().toString(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the buffer used for building the transformed output. | ||
| - * | ||
| - * @return A non-null string builder, possibly empty. | ||
| - */ | ||
| - private StringBuilder getBuffer() { | ||
| - return this.buffer; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Answers whether the transformed query should be indented with newlines. | ||
| - * | ||
| - * @return true Format the output (by default). | ||
| - */ | ||
| - protected boolean beautify() { | ||
| - // Returns the System property value. | ||
| - return Boolean.getBoolean( "beautify" ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * When beautify is enabled, this returns a newline followed by | ||
| - * the default amount ofindentation; when disabled, this returns a single | ||
| - * space. | ||
| - * | ||
| - * @return A newline followed by spaces or a single space. | ||
| - */ | ||
| - protected String getNewlineIndent() { | ||
| - return getNewline() + (beautify() ? getDefaultIndent() : " "); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates a string of spaces that is spaces spaces long. | ||
| - * | ||
| - * @param spaces The number of spaces to add to the string. | ||
| - * @return A string with 'spaces' number of " " padding. | ||
| - */ | ||
| - protected String getIndent( int spaces ) { | ||
| - return CharBuffer.allocate( spaces ).toString().replace( '\0', ' ' ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the system newline separator character sequence. | ||
| - * | ||
| - * @return CR/LF on Windows, LF on Unix, CR on MacOS, or " " if beautify | ||
| - * is set true. | ||
| - */ | ||
| - protected String getNewline() { | ||
| - return beautify() ? System.lineSeparator() : " "; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the default amount of spaces. | ||
| - * | ||
| - * @return " " (by default) | ||
| - */ | ||
| - protected String getDefaultIndent() { | ||
| - return getIndent( getIndentBy() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the number of spaces to indent by. | ||
| - * | ||
| - * @return 2 (by default) | ||
| - */ | ||
| - protected int getIndentBy() { | ||
| - return INDENT; | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| -import com.whitemagicsoftware.rxm.tree.Token; | ||
| -import com.whitemagicsoftware.rxm.tree.Tree; | ||
| - | ||
| -import org.antlr.v4.runtime.ParserRuleContext; | ||
| - | ||
| -/** | ||
| - * Transforms <b><code>column > path</code></b> into an | ||
| - * <code>XMLELEMENT</code> expression. | ||
| - */ | ||
| -public class ColumnMapContext extends Context { | ||
| - /** | ||
| - * Default constructor (calls super). | ||
| - * | ||
| - * @param ctx The payload that associates the parser rule context with | ||
| - * the abstract syntax tree. | ||
| - */ | ||
| - public ColumnMapContext( ParserRuleContext ctx ) { | ||
| - super( ctx ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Opens the XMLELEMENT. | ||
| - * | ||
| - * @return ",XMLELEMENT(" ...* | ||
| - */ | ||
| - @Override | ||
| - public Token getStart() { | ||
| - return new Token( | ||
| - String.format( ",%s,%s.%s", | ||
| - startElement( path() ), | ||
| - getParentTableText(), | ||
| - getColumnText() | ||
| - ) | ||
| - ); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public Token getStop() { | ||
| - return new Token( | ||
| - String.format( "%s%s", | ||
| - stopElement( path() ), | ||
| - super.getStop().toString() | ||
| - ) | ||
| - ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the path from the column map context, rather than using | ||
| - * the superclass's table map context. | ||
| - * | ||
| - * @return The path associated with the column map parser rule. | ||
| - */ | ||
| - protected QueryParser.PathContext path() { | ||
| - return getColumnMapContext().path(); | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.tree.Tree; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| - | ||
| -import org.antlr.v4.runtime.ParserRuleContext; | ||
| -import org.antlr.v4.runtime.tree.TerminalNode; | ||
| - | ||
| -/** | ||
| - * Transforms <b><code>table.column +> table.column</code></b> into a SQL | ||
| - * <code>INNER JOIN</code> expression; similarly, transforms the operand | ||
| - * <code>-></code> into <code>OUTER JOIN</code>. | ||
| - */ | ||
| -public class JoinClause extends Clause { | ||
| - /** | ||
| - * Default constructor. | ||
| - */ | ||
| - public JoinClause() { | ||
| - } | ||
| - | ||
| - @Override | ||
| - public void enterJoinMap( QueryParser.JoinMapContext ctx ) { | ||
| - QueryParser.TableColumnContext | ||
| - lhs = ctx.tableColumn( 0 ), | ||
| - rhs = ctx.tableColumn( 1 ); | ||
| - | ||
| - String lhsTable = lhs.table().getText(); | ||
| - | ||
| - append( String.format( "%s JOIN %s %s ON%s%s%s = %s%s%s", | ||
| - ctx.T_OUTER() == null ? "INNER" : "OUTER", | ||
| - lhsTable, | ||
| - lhsTable, | ||
| - getNewlineIndent(), | ||
| - lhsTable, | ||
| - lhs.column().getText(), | ||
| - rhs.table().getText(), | ||
| - rhs.column().getText(), | ||
| - getNewline() ) | ||
| - ); | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| -import com.whitemagicsoftware.rxm.tree.Token; | ||
| - | ||
| -import org.antlr.v4.runtime.ParserRuleContext; | ||
| - | ||
| -/** | ||
| - * Transforms <b><code>root > element</code></b> into the starting | ||
| - * <code>SELECT</code> statement. | ||
| - */ | ||
| -public class RootContext extends Context { | ||
| - public RootContext( ParserRuleContext ctx ) { | ||
| - super( ctx ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Transforms into SELECT, XMLROOT, and XMLELEMENT. | ||
| - * | ||
| - * @return "SELECT XMLROOT(" ... + ",XMLELEMENT(" ... | ||
| - */ | ||
| - @Override | ||
| - public Token getStart() { | ||
| - QueryParser.ElementContext element = getElementContext(); | ||
| - String e = element.getText(); | ||
| - | ||
| - String s = String.format( "XMLROOT(%s", startElement( e ) ); | ||
| - return new Token( s ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Closes the XMLELEMENT and XMLROOT. | ||
| - */ | ||
| - @Override | ||
| - public Token getStop() { | ||
| - return new Token( String.format( ")%s)", getDeclaration() ) ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the value for the XML declaration. | ||
| - * | ||
| - * @return The XML declaration string in SQL/XML. | ||
| - */ | ||
| - protected String getDeclaration() { | ||
| - return String.format( ",VERSION '%.1f',STANDALONE %s", | ||
| - getVersion(), isStandalone() ? "YES" : "NO" ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the XML version number, used for the <code>VERSION</code> value. | ||
| - * | ||
| - * @return 1.0f by default. | ||
| - */ | ||
| - protected float getVersion() { | ||
| - return 1.0f; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Indicates the value for <code>STANDALONE</code>. | ||
| - * | ||
| - * @return <code>true</code> STANDALONE is set <code>YES</code>; | ||
| - * <code>false</code> STANDALONE is set <code>NO</code>. | ||
| - */ | ||
| - protected boolean isStandalone() { | ||
| - return true; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Casts the payload into the proper context, then extracts the element. | ||
| - * | ||
| - * @return The element associated with the root parser rule. | ||
| - */ | ||
| - protected QueryParser.ElementContext getElementContext() { | ||
| - return ((QueryParser.RootContext)getParserRuleContext()).element(); | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| -import com.whitemagicsoftware.rxm.tree.Tree; | ||
| - | ||
| -import org.antlr.v4.runtime.ParserRuleContext; | ||
| - | ||
| -public class SelectClause extends Clause { | ||
| - private Tree<ParserRuleContext> tree; | ||
| - | ||
| - /** | ||
| - * Default constructor. | ||
| - */ | ||
| - public SelectClause() { | ||
| - } | ||
| - | ||
| - /** | ||
| - * Adds the root context node to the top-most part of the tree. This | ||
| - * initializes the context tree to the root context node. | ||
| - * | ||
| - * @param ctx The payload to transform. | ||
| - */ | ||
| - @Override | ||
| - public void enterRoot( QueryParser.RootContext ctx ) { | ||
| - setTree( createTree( new RootContext( ctx ) ) ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Invoked when parsing <b><code>table > path</code></b>. Adds a new | ||
| - * leaf to the context tree, then changes the context tree to the newly | ||
| - * added leaf. | ||
| - * | ||
| - * @param ctx The payload to transform. | ||
| - */ | ||
| - @Override | ||
| - public void enterTableMap( QueryParser.TableMapContext ctx ) { | ||
| - setTree( addLeaf( new TableMapContext( ctx ) ) ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Invoked when parsing <b><code>column > path</code></b>. Adds a new | ||
| - * leaf to the context tree. | ||
| - * | ||
| - * @param ctx The payload to transform. | ||
| - */ | ||
| - @Override | ||
| - public void enterColumnMap( | ||
| - QueryParser.ColumnMapContext ctx ) { | ||
| - addLeaf( new ColumnMapContext( ctx ) ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Invoked when parsing <b><code>column > attribute</code></b>. Adds | ||
| - * a new leaf to the context tree. | ||
| - * | ||
| - * @param ctx The payload to transform. | ||
| - */ | ||
| - @Override | ||
| - public void enterAttributeMap( | ||
| - QueryParser.AttributeMapContext ctx ) { | ||
| - addLeaf( new AttributeMapContext( ctx ) ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Invoked when parsing <b><code>^</code></b>. Changes context tree to | ||
| - * its parent. If the parent doesn't exist, this will fail silently. | ||
| - * | ||
| - * @param ctx Indicates that context should switch, has no payload. | ||
| - */ | ||
| - @Override | ||
| - public void enterPop( QueryParser.PopContext ctx ) { | ||
| - Tree<ParserRuleContext> parent = getTree().getParent(); | ||
| - | ||
| - if( parent != null ) { | ||
| - setTree( parent ); | ||
| - } | ||
| - } | ||
| - | ||
| - /** | ||
| - * Called after the last mapping statement is found. This builds the | ||
| - * SELECT statement using the tree created while walking the <b>rxm</b> | ||
| - * source. | ||
| - * | ||
| - * @param ctx Unused. | ||
| - */ | ||
| - @Override | ||
| - public void exitStatements( QueryParser.StatementsContext ctx ) { | ||
| - append( getTree().getRoot().toString() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Adds the given parser rule context to the current context tree. | ||
| - * | ||
| - * @param ctx The parser rule context to add as a leaf to the context tree. | ||
| - * @return The leaf (tree) that was added. | ||
| - */ | ||
| - private Tree<ParserRuleContext> addLeaf( ParserRuleContext ctx ) { | ||
| - return getTree().addLeaf( createTree( ctx ) ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Changes where the next mapped items will be attached. | ||
| - */ | ||
| - private void setTree( Tree<ParserRuleContext> tree ) { | ||
| - this.tree = tree; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns where to attach upcoming map items. | ||
| - */ | ||
| - private Tree<ParserRuleContext> getTree() { | ||
| - return this.tree; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates a new tree with the given parser rule context. This is required | ||
| - * so that the *Context classes are aware of their relative position in the | ||
| - * hierarchy. As a consequence of that knowledge, the objects have access | ||
| - * to the nearest table name, whether siblings exist, etc. | ||
| - * | ||
| - * @param ctx The context to wrap in a payload that is added to a tree. | ||
| - * | ||
| - * @return A new tree with a single data element and no branches. | ||
| - */ | ||
| - private Tree<ParserRuleContext> createTree( ParserRuleContext ctx ) { | ||
| - return new SelectTree( ctx ); | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| -import com.whitemagicsoftware.rxm.tree.Token; | ||
| - | ||
| -import org.antlr.v4.runtime.ParserRuleContext; | ||
| - | ||
| -/** | ||
| - * Transforms <b><code>table > path</code></b> into an | ||
| - * <code>XMLELEMENT</code> expression. | ||
| - */ | ||
| -public class TableMapContext extends Context { | ||
| - public TableMapContext( ParserRuleContext ctx ) { | ||
| - super( ctx ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Opens the XMLELEMENT. | ||
| - * | ||
| - * @return ",XMLELEMENT(" ... | ||
| - */ | ||
| - @Override | ||
| - public Token getStart() { | ||
| - ParserRuleContext p = getParserRuleContext(); | ||
| - QueryParser.TableMapContext ctx = (QueryParser.TableMapContext)p; | ||
| - String tableName = ctx.table(1).getText(); | ||
| - | ||
| - return new Token( String.format( ",%s", startElement( tableName ) ) ); | ||
| - } | ||
| -} | ||
| - | ||
| -package com.whitemagicsoftware.rxm.tree.xml; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| - | ||
| -import org.antlr.v4.runtime.tree.TerminalNode; | ||
| - | ||
| -/** | ||
| - */ | ||
| -public class WhereClause extends Clause { | ||
| - /** | ||
| - * Default constructor. | ||
| - */ | ||
| - public WhereClause() { | ||
| - } | ||
| - | ||
| - /** | ||
| - * Appends "<code>WHERE </code>" to the output. | ||
| - * | ||
| - * @param ctx The WHERE expression context (unused). | ||
| - */ | ||
| - public void enterWhere( QueryParser.WhereContext ctx ) { | ||
| - append( "WHERE" ).append( getNewlineIndent() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Appends "<code> AND </code>" to the output. | ||
| - * | ||
| - * @param ctx The AND expression context (unused). | ||
| - */ | ||
| - public void exitExprAnd( QueryParser.ExprAndContext ctx ) { | ||
| - append( getNewlineIndent() ).append( "AND " ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Appends "<code> OR </code>" to the output. | ||
| - * | ||
| - * @param ctx The OR expression context (unused). | ||
| - */ | ||
| - public void exitExprOr( QueryParser.ExprOrContext ctx ) { | ||
| - append( getNewlineIndent() ).append( "OR " ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Appends "<code>(</code>" to the output. | ||
| - * | ||
| - * @param ctx The parenthesis expression context (unused). | ||
| - */ | ||
| - public void enterExprParen( QueryParser.ExprParenContext ctx ) { | ||
| - append( "(" ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Appends "<code>)</code>" to the output. | ||
| - * | ||
| - * @param ctx The parenthesis expression context (unused). | ||
| - */ | ||
| - public void exitExprParen( QueryParser.ExprParenContext ctx ) { | ||
| - append( ")" ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Transforms an <b>rxm</b> equality comparator into ANSI SQL. | ||
| - * Note: There is probably a cleaner way to implement this monstrosity. | ||
| - * | ||
| - * @param ctx The equality comparison context. | ||
| - */ | ||
| - public void enterExprCompEqual( QueryParser.ExprCompEqualContext ctx ) { | ||
| - QueryParser.TableColumnContext tableColumn = ctx.tableColumn(); | ||
| - QueryParser.ExprSetContext set = ctx.exprSet(); | ||
| - | ||
| - // If the terminal node is null, then the equality is T_INEQ. | ||
| - TerminalNode equals = ctx.getToken( QueryParser.T_EQ, 0 ); | ||
| - | ||
| - String entity = tableColumn.getText(); | ||
| - String equalTo = (equals == null) ? "NOT " : ""; | ||
| - | ||
| - if( set == null ) { | ||
| - // Presume = or <> by default (changes to IS or IS NOT as needed). | ||
| - String comparator = ctx.getChild(1).getText(); | ||
| - QueryParser.LiteralContext literal = ctx.exprValue().literal(); | ||
| - | ||
| - if( literal == null ) { | ||
| - // No literal value to examine means it is probably a parameter. | ||
| - append( ctx.getText() ); | ||
| - } | ||
| - else { | ||
| - // Default is to presume the value literal is not the 'null' token. | ||
| - String rhs = literal.getText(); | ||
| - | ||
| - // If the "null" literal terminal node is not null, then null has | ||
| - // been found and so the value must be set to NULL and the | ||
| - // comparators changed from symbols to ANSI SQL keywords. | ||
| - if( literal.T_NULL() != null ) { | ||
| - rhs = "NULL"; | ||
| - comparator = " IS " + equalTo; | ||
| - } | ||
| - | ||
| - append( String.format( "%s%s%s", entity, comparator, rhs ) ); | ||
| - } | ||
| - } | ||
| - else { | ||
| - // Transform to ANSI SQL set notation. | ||
| - append( String.format( "%s %sIN (%s)", | ||
| - entity, | ||
| - equalTo, | ||
| - set.exprList().getText() ) ); | ||
| - } | ||
| - } | ||
| - | ||
| - /** | ||
| - * Appends a relational expression (less than, greater than, etc.) to the | ||
| - * output. | ||
| - * | ||
| - * @param ctx The relational expression context. | ||
| - */ | ||
| - public void enterExprCompRel( QueryParser.ExprCompRelContext ctx ) { | ||
| - append( ctx.getText() ); | ||
| - } | ||
| -} | ||
| - | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| +import com.whitemagicsoftware.rxm.tree.Tree; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| + | ||
| +/** | ||
| + * Transforms <b><code>table > @attribute</code></b> into an | ||
| + * <code>XMLATTRIBUTES</code> expression. | ||
| + */ | ||
| +public class AttributeMapContext extends Context { | ||
| + private boolean hasPrecedingPayload = false; | ||
| + private boolean hasFollowingPayload = false; | ||
| + | ||
| + public AttributeMapContext( ParserRuleContext ctx ) { | ||
| + super( ctx ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Opens the XMLATTRIBUTES expression. | ||
| + * | ||
| + * @return Token containing ",XMLATTRIBUTES(" ... | ||
| + */ | ||
| + @Override | ||
| + public Token getStart() { | ||
| + String | ||
| + tableName = getParentTableText(), | ||
| + columnName = getColumnText(), | ||
| + attributeName = getAttributeText(); | ||
| + | ||
| + return new Token( | ||
| + String.format( ",%s", hasPrecedingPayload() ? | ||
| + nextAttribute( tableName, columnName, attributeName ) : | ||
| + firstAttribute( tableName, columnName, attributeName ) | ||
| + ) | ||
| + ); | ||
| + } | ||
| + | ||
| + private boolean hasPrecedingPayload() { | ||
| + return this.hasPrecedingPayload; | ||
| + } | ||
| + | ||
| + private boolean hasFollowingPayload() { | ||
| + return this.hasFollowingPayload; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Closes the XMLATTRIBUTES expression, should no more attributes be | ||
| + * forthcoming. | ||
| + * | ||
| + * @return Token containing ")" or the empty string. | ||
| + */ | ||
| + @Override | ||
| + public Token getStop() { | ||
| + return new Token( hasFollowingPayload() ? "" : ")" ); | ||
| + } | ||
| + | ||
| + private String getAttributeText() { | ||
| + return attribute().getChild(1).getText(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the attribute (node) name. | ||
| + */ | ||
| + private QueryParser.AttributeContext attribute() { | ||
| + return getAttributeMapContext().attribute(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the attribute map context that this class wraps. | ||
| + */ | ||
| + private QueryParser.AttributeMapContext getAttributeMapContext() { | ||
| + return (QueryParser.AttributeMapContext)getParserRuleContext(); | ||
| + } | ||
| + | ||
| + @Override | ||
| + protected QueryParser.ColumnContext column() { | ||
| + return getAttributeMapContext().column(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Formats the start of the XMLATTRIBUTES call. | ||
| + * | ||
| + * @param table The table name assigned to the attribute. | ||
| + * @param column The column name assigned to the attribute. | ||
| + * @param node The document node name assigned to the attribute. | ||
| + * | ||
| + * @return The text used for SQL/XML XMLATTRIBUTES calls. | ||
| + */ | ||
| + private String firstAttribute( String table, String column, String node ) { | ||
| + return "XMLATTRIBUTES(" + nextAttribute( table, column, node ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Formats the next attribute of the XMLATTRIBUTES call. This is only | ||
| + * called when there are contiguous attributes listed in the <b>rxm</b>. | ||
| + * | ||
| + * @param table The table name assigned to the attribute. | ||
| + * @param column The column name assigned to the attribute. | ||
| + * @param node The document node name assigned to the attribute. | ||
| + * | ||
| + * @return The text used for SQL/XML XMLATTRIBUTES calls. | ||
| + */ | ||
| + private String nextAttribute( String table, String column, String node ) { | ||
| + String result = "%s.%s%s"; | ||
| + | ||
| + // If the column name and attribute name are the same, then the | ||
| + // "AS" clause is superfluous. | ||
| + if( column.equals( node ) ) { | ||
| + result = String.format( result, table, column, "" ); | ||
| + } | ||
| + else { | ||
| + node = String.format( " AS \"%s\"", node ); | ||
| + result = String.format( result, table, column, node ); | ||
| + } | ||
| + | ||
| + return result; | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import java.nio.CharBuffer; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| + | ||
| +/** | ||
| + * Common behaviour for various clauses. | ||
| + */ | ||
| +public class Clause extends QueryBaseListener { | ||
| + private static int INDENT = 2; | ||
| + | ||
| + private StringBuilder buffer = new StringBuilder( 2048 ); | ||
| + | ||
| + /** | ||
| + * Default constructor. | ||
| + */ | ||
| + public Clause() { | ||
| + } | ||
| + | ||
| + /** | ||
| + * Appends a given string to the buffer. | ||
| + * | ||
| + * @param s The string to append. | ||
| + * @return this to chain the append calls. | ||
| + */ | ||
| + protected Clause append( String s ) { | ||
| + getBuffer().append( s ); | ||
| + return this; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the buffer converted to a string. This is the result of | ||
| + * the transformed output and should only be called when the parsing | ||
| + * of the <b>rxm</b> source is complete. | ||
| + * | ||
| + * @return A non-null string, possibly empty. | ||
| + */ | ||
| + public String toString() { | ||
| + return getBuffer().toString(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the buffer used for building the transformed output. | ||
| + * | ||
| + * @return A non-null string builder, possibly empty. | ||
| + */ | ||
| + private StringBuilder getBuffer() { | ||
| + return this.buffer; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Answers whether the transformed query should be indented with newlines. | ||
| + * | ||
| + * @return true Format the output (by default). | ||
| + */ | ||
| + protected boolean beautify() { | ||
| + // Returns the System property value. | ||
| + return Boolean.getBoolean( "beautify" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * When beautify is enabled, this returns a newline followed by | ||
| + * the default amount ofindentation; when disabled, this returns a single | ||
| + * space. | ||
| + * | ||
| + * @return A newline followed by spaces or a single space. | ||
| + */ | ||
| + protected String getNewlineIndent() { | ||
| + return getNewline() + (beautify() ? getDefaultIndent() : " "); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates a string of spaces that is spaces spaces long. | ||
| + * | ||
| + * @param spaces The number of spaces to add to the string. | ||
| + * @return A string with 'spaces' number of " " padding. | ||
| + */ | ||
| + protected String getIndent( int spaces ) { | ||
| + return CharBuffer.allocate( spaces ).toString().replace( '\0', ' ' ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the system newline separator character sequence. | ||
| + * | ||
| + * @return CR/LF on Windows, LF on Unix, CR on MacOS, or " " if beautify | ||
| + * is set true. | ||
| + */ | ||
| + protected String getNewline() { | ||
| + return beautify() ? System.lineSeparator() : " "; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the default amount of spaces. | ||
| + * | ||
| + * @return " " (by default) | ||
| + */ | ||
| + protected String getDefaultIndent() { | ||
| + return getIndent( getIndentBy() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the number of spaces to indent by. | ||
| + * | ||
| + * @return 2 (by default) | ||
| + */ | ||
| + protected int getIndentBy() { | ||
| + return INDENT; | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| +import com.whitemagicsoftware.rxm.tree.Tree; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| + | ||
| +/** | ||
| + * Transforms <b><code>column > path</code></b> into an | ||
| + * <code>XMLELEMENT</code> expression. | ||
| + */ | ||
| +public class ColumnMapContext extends Context { | ||
| + /** | ||
| + * Default constructor (calls super). | ||
| + * | ||
| + * @param ctx The payload that associates the parser rule context with | ||
| + * the abstract syntax tree. | ||
| + */ | ||
| + public ColumnMapContext( ParserRuleContext ctx ) { | ||
| + super( ctx ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Opens the XMLELEMENT. | ||
| + * | ||
| + * @return ",XMLELEMENT(" ...* | ||
| + */ | ||
| + @Override | ||
| + public Token getStart() { | ||
| + return new Token( | ||
| + String.format( ",%s,%s.%s", | ||
| + startElement( path() ), | ||
| + getParentTableText(), | ||
| + getColumnText() | ||
| + ) | ||
| + ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public Token getStop() { | ||
| + return new Token( | ||
| + String.format( "%s%s", | ||
| + stopElement( path() ), | ||
| + super.getStop().toString() | ||
| + ) | ||
| + ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the path from the column map context, rather than using | ||
| + * the superclass's table map context. | ||
| + * | ||
| + * @return The path associated with the column map parser rule. | ||
| + */ | ||
| + protected QueryParser.PathContext path() { | ||
| + return getColumnMapContext().path(); | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import java.util.List; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.tree.Tree; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| + | ||
| +/** | ||
| + * Represents a wrapper class for ParserRuleContext. | ||
| + */ | ||
| +public abstract class Context extends ParserRuleContext { | ||
| + private ParserRuleContext parserRuleContext; | ||
| + | ||
| + /** | ||
| + * Constructs a wrapper class that provides the ParserRuleContext and | ||
| + * tree for the *Context classes. | ||
| + * | ||
| + * @param payload The data that contains the ParserRuleContext. | ||
| + */ | ||
| + protected Context( ParserRuleContext payload ) { | ||
| + setParserRuleContext( payload ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Opens the current transformation expression. Subclasses must override | ||
| + * this to provide the correct opening SQL/XML expression. It could be | ||
| + * made abstract, but is a useful convenience method. | ||
| + * | ||
| + * @return "(" | ||
| + */ | ||
| + @Override | ||
| + public Token getStart() { | ||
| + return new Token( "(" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Closes the current transformation expression. Subclasses may override | ||
| + * this to provide a custom closing SQL/XML expression. | ||
| + * | ||
| + * @return ")" | ||
| + */ | ||
| + @Override | ||
| + public Token getStop() { | ||
| + return new Token( ")" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Formats the start of the XMLELEMENT call. | ||
| + * | ||
| + * @param ctx The name assigned to the element. | ||
| + * | ||
| + * @return The text used for SQL/XML XMLELEMENT calls. | ||
| + */ | ||
| + protected String startElement( QueryParser.PathContext ctx ) { | ||
| + QueryParser.ElementContext element = ctx.element(); | ||
| + | ||
| + return element == null ? | ||
| + startElement( ctx.elementPath() ) : | ||
| + startElement( element ); | ||
| + } | ||
| + | ||
| + protected String startElement( QueryParser.ElementPathContext ctx ) { | ||
| + List<QueryParser.ElementContext> elements = ctx.element(); | ||
| + StringBuilder sb = new StringBuilder( 2048 ); | ||
| + String sep = ""; | ||
| + | ||
| + for( QueryParser.ElementContext element : elements ) { | ||
| + sb.append( sep ); | ||
| + sb.append( startElement( element ) ); | ||
| + sep = ","; | ||
| + } | ||
| + | ||
| + return sb.toString(); | ||
| + } | ||
| + | ||
| + protected String startElement( QueryParser.ElementContext ctx ) { | ||
| + return startElement( ctx.getText() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Formats the start of the XMLELEMENT call. | ||
| + * | ||
| + * @param name The name assigned to the element. | ||
| + * | ||
| + * @return The text used for SQL/XML XMLELEMENT calls. | ||
| + */ | ||
| + protected String startElement( String name ) { | ||
| + return String.format( "XMLELEMENT(NAME \"%s\"", name ); | ||
| + } | ||
| + | ||
| + protected String stopElement( QueryParser.PathContext ctx ) { | ||
| + QueryParser.ElementContext element = ctx.element(); | ||
| + | ||
| + return element == null ? stopElement( ctx.elementPath() ) : ""; | ||
| + } | ||
| + | ||
| + protected String stopElement( QueryParser.ElementPathContext ctx ) { | ||
| + List<QueryParser.ElementContext> elements = ctx.element(); | ||
| + StringBuilder sb = new StringBuilder( 2048 ); | ||
| + String sep = ""; | ||
| + | ||
| + for( QueryParser.ElementContext element : elements ) { | ||
| + sb.append( sep ); | ||
| + sep = ")"; | ||
| + } | ||
| + | ||
| + return sb.toString(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * This will return column name without the preceding period. | ||
| + * | ||
| + * @return The column name, without a leading period. | ||
| + */ | ||
| + protected String getColumnText() { | ||
| + // The first child (index 0) is the leading period. | ||
| + // The second child (index 1) is the column name. | ||
| + return column().getChild(1).getText(); | ||
| + } | ||
| + | ||
| + protected String getParentTableText() { | ||
| + return getPayload().getText(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the column name, if any. This should not be called | ||
| + * if the parser rule context doesn't know about columns. | ||
| + * | ||
| + * @return A column instance, but could throw a class cast exception. | ||
| + */ | ||
| + protected QueryParser.ColumnContext column() { | ||
| + return getColumnMapContext().column(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the table associated with the payload. | ||
| + * | ||
| + * @return The table from the table map context. | ||
| + */ | ||
| + protected QueryParser.TableContext table() { | ||
| + return getTableMapContext().table(0); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Casts the payload into the proper context, then extracts the column. | ||
| + * | ||
| + * @return The column associated with the column map parser rule. | ||
| + */ | ||
| + protected QueryParser.ColumnMapContext getColumnMapContext() { | ||
| + return (QueryParser.ColumnMapContext)getParserRuleContext(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the nearest table context to this parser rule context. | ||
| + * | ||
| + * @return The wrapped parser rule context's table map context. | ||
| + */ | ||
| + protected QueryParser.TableMapContext getTableMapContext() { | ||
| + return (QueryParser.TableMapContext)getParserRuleContext(); | ||
| + } | ||
| + | ||
| + private void setParserRuleContext( ParserRuleContext ctx ) { | ||
| + this.parserRuleContext = ctx; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the payload used to associate the parser rule context with | ||
| + * a tree. | ||
| + * | ||
| + * @return The payload containing a parser rule context and tree. | ||
| + */ | ||
| + protected ParserRuleContext getParserRuleContext() { | ||
| + return this.parserRuleContext; | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.QueryBuilder; | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| + | ||
| +/** | ||
| + * Transforms the first <b><code>table > table</code></b> into a SQL | ||
| + * <code>FROM</code> expression. | ||
| + */ | ||
| +public class FromClause extends Clause { | ||
| + private QueryBuilder builder; | ||
| + | ||
| + /** | ||
| + * Default constructor. | ||
| + * | ||
| + * @param builder Required to stop listening to events once the first | ||
| + * table is obtained. | ||
| + */ | ||
| + public FromClause( QueryBuilder builder ) { | ||
| + this.builder = builder; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns true if the from clause is ready for writing. | ||
| + * | ||
| + * @return true Stop listening for events. | ||
| + */ | ||
| + public boolean complete() { | ||
| + return toString().length() > 0; | ||
| + } | ||
| + | ||
| + @Override | ||
| + public void enterTableMap( QueryParser.TableMapContext ctx ) { | ||
| + String tableName = ctx.table(0).getText(); | ||
| + | ||
| + append( | ||
| + String.format( "FROM%s %s %s%s", | ||
| + getNewlineIndent(), | ||
| + tableName, | ||
| + tableName, | ||
| + getNewline() | ||
| + ) | ||
| + ); | ||
| + | ||
| + getBuilder().rescindEvents( this ); | ||
| + } | ||
| + | ||
| + private QueryBuilder getBuilder() { | ||
| + return this.builder; | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.tree.Tree; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| +import org.antlr.v4.runtime.tree.TerminalNode; | ||
| + | ||
| +/** | ||
| + * Transforms <b><code>table.column +> table.column</code></b> into a SQL | ||
| + * <code>INNER JOIN</code> expression; similarly, transforms the operand | ||
| + * <code>-></code> into <code>OUTER JOIN</code>. | ||
| + */ | ||
| +public class JoinClause extends Clause { | ||
| + /** | ||
| + * Default constructor. | ||
| + */ | ||
| + public JoinClause() { | ||
| + } | ||
| + | ||
| + @Override | ||
| + public void enterJoinMap( QueryParser.JoinMapContext ctx ) { | ||
| + QueryParser.TableColumnContext | ||
| + lhs = ctx.tableColumn( 0 ), | ||
| + rhs = ctx.tableColumn( 1 ); | ||
| + | ||
| + String lhsTable = lhs.table().getText(); | ||
| + | ||
| + append( String.format( "%s JOIN %s %s ON%s%s%s = %s%s%s", | ||
| + ctx.T_OUTER() == null ? "INNER" : "OUTER", | ||
| + lhsTable, | ||
| + lhsTable, | ||
| + getNewlineIndent(), | ||
| + lhsTable, | ||
| + lhs.column().getText(), | ||
| + rhs.table().getText(), | ||
| + rhs.column().getText(), | ||
| + getNewline() ) | ||
| + ); | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| + | ||
| +/** | ||
| + * Transforms <b><code>root > element</code></b> into the starting | ||
| + * <code>SELECT</code> statement. | ||
| + */ | ||
| +public class RootContext extends Context { | ||
| + public RootContext( ParserRuleContext ctx ) { | ||
| + super( ctx ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Transforms into SELECT, XMLROOT, and XMLELEMENT. | ||
| + * | ||
| + * @return "SELECT XMLROOT(" ... + ",XMLELEMENT(" ... | ||
| + */ | ||
| + @Override | ||
| + public Token getStart() { | ||
| + QueryParser.ElementContext element = getElementContext(); | ||
| + String e = element.getText(); | ||
| + | ||
| + String s = String.format( "XMLROOT(%s", startElement( e ) ); | ||
| + return new Token( s ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Closes the XMLELEMENT and XMLROOT. | ||
| + */ | ||
| + @Override | ||
| + public Token getStop() { | ||
| + return new Token( String.format( ")%s)", getDeclaration() ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the value for the XML declaration. | ||
| + * | ||
| + * @return The XML declaration string in SQL/XML. | ||
| + */ | ||
| + protected String getDeclaration() { | ||
| + return String.format( ",VERSION '%.1f',STANDALONE %s", | ||
| + getVersion(), isStandalone() ? "YES" : "NO" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the XML version number, used for the <code>VERSION</code> value. | ||
| + * | ||
| + * @return 1.0f by default. | ||
| + */ | ||
| + protected float getVersion() { | ||
| + return 1.0f; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Indicates the value for <code>STANDALONE</code>. | ||
| + * | ||
| + * @return <code>true</code> STANDALONE is set <code>YES</code>; | ||
| + * <code>false</code> STANDALONE is set <code>NO</code>. | ||
| + */ | ||
| + protected boolean isStandalone() { | ||
| + return true; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Casts the payload into the proper context, then extracts the element. | ||
| + * | ||
| + * @return The element associated with the root parser rule. | ||
| + */ | ||
| + protected QueryParser.ElementContext getElementContext() { | ||
| + return ((QueryParser.RootContext)getParserRuleContext()).element(); | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| +import com.whitemagicsoftware.rxm.tree.Tree; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| + | ||
| +public class SelectClause extends Clause { | ||
| + private Tree<ParserRuleContext> tree; | ||
| + | ||
| + /** | ||
| + * Default constructor. | ||
| + */ | ||
| + public SelectClause() { | ||
| + } | ||
| + | ||
| + /** | ||
| + * Adds the root context node to the top-most part of the tree. This | ||
| + * initializes the context tree to the root context node. | ||
| + * | ||
| + * @param ctx The payload to transform. | ||
| + */ | ||
| + @Override | ||
| + public void enterRoot( QueryParser.RootContext ctx ) { | ||
| + setTree( createTree( new RootContext( ctx ) ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Invoked when parsing <b><code>table > path</code></b>. Adds a new | ||
| + * leaf to the context tree, then changes the context tree to the newly | ||
| + * added leaf. | ||
| + * | ||
| + * @param ctx The payload to transform. | ||
| + */ | ||
| + @Override | ||
| + public void enterTableMap( QueryParser.TableMapContext ctx ) { | ||
| + setTree( addLeaf( new TableMapContext( ctx ) ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Invoked when parsing <b><code>column > path</code></b>. Adds a new | ||
| + * leaf to the context tree. | ||
| + * | ||
| + * @param ctx The payload to transform. | ||
| + */ | ||
| + @Override | ||
| + public void enterColumnMap( | ||
| + QueryParser.ColumnMapContext ctx ) { | ||
| + addLeaf( new ColumnMapContext( ctx ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Invoked when parsing <b><code>column > attribute</code></b>. Adds | ||
| + * a new leaf to the context tree. | ||
| + * | ||
| + * @param ctx The payload to transform. | ||
| + */ | ||
| + @Override | ||
| + public void enterAttributeMap( | ||
| + QueryParser.AttributeMapContext ctx ) { | ||
| + addLeaf( new AttributeMapContext( ctx ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Invoked when parsing <b><code>^</code></b>. Changes context tree to | ||
| + * its parent. If the parent doesn't exist, this will fail silently. | ||
| + * | ||
| + * @param ctx Indicates that context should switch, has no payload. | ||
| + */ | ||
| + @Override | ||
| + public void enterPop( QueryParser.PopContext ctx ) { | ||
| + Tree<ParserRuleContext> parent = getTree().getParent(); | ||
| + | ||
| + if( parent != null ) { | ||
| + setTree( parent ); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Called after the last mapping statement is found. This builds the | ||
| + * SELECT statement using the tree created while walking the <b>rxm</b> | ||
| + * source. | ||
| + * | ||
| + * @param ctx Unused. | ||
| + */ | ||
| + @Override | ||
| + public void exitStatements( QueryParser.StatementsContext ctx ) { | ||
| + append( getTree().getRoot().toString() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Adds the given parser rule context to the current context tree. | ||
| + * | ||
| + * @param ctx The parser rule context to add as a leaf to the context tree. | ||
| + * @return The leaf (tree) that was added. | ||
| + */ | ||
| + private Tree<ParserRuleContext> addLeaf( ParserRuleContext ctx ) { | ||
| + return getTree().addLeaf( createTree( ctx ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Changes where the next mapped items will be attached. | ||
| + */ | ||
| + private void setTree( Tree<ParserRuleContext> tree ) { | ||
| + this.tree = tree; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns where to attach upcoming map items. | ||
| + */ | ||
| + private Tree<ParserRuleContext> getTree() { | ||
| + return this.tree; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates a new tree with the given parser rule context. This is required | ||
| + * so that the *Context classes are aware of their relative position in the | ||
| + * hierarchy. As a consequence of that knowledge, the objects have access | ||
| + * to the nearest table name, whether siblings exist, etc. | ||
| + * | ||
| + * @param ctx The context to wrap in a payload that is added to a tree. | ||
| + * | ||
| + * @return A new tree with a single data element and no branches. | ||
| + */ | ||
| + private Tree<ParserRuleContext> createTree( ParserRuleContext ctx ) { | ||
| + return new SelectTree( ctx ); | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import java.nio.CharBuffer; | ||
| + | ||
| +import java.util.ArrayList; | ||
| +import java.util.List; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.tree.Tree; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| +import org.antlr.v4.runtime.Token; | ||
| + | ||
| +/** | ||
| + * This creates a tree capable of generating a SQL/XML SELECT clause. | ||
| + */ | ||
| +public class SelectTree extends Tree<ParserRuleContext> { | ||
| + public static final int INDENT = 2; | ||
| + | ||
| + /** | ||
| + * Constructs a tree capable of transforming itself into a SQL/XML | ||
| + * SELECT clause. | ||
| + * | ||
| + * @param payload The data associated with this tree leaf (must not | ||
| + * be null). | ||
| + */ | ||
| + public SelectTree( ParserRuleContext payload ) { | ||
| + super( payload ); | ||
| + } | ||
| + | ||
| + public String toString() { | ||
| + return toString( this, 0 ); | ||
| + } | ||
| + | ||
| + /* | ||
| + * This converts all payload instances in the tree to their transformed | ||
| + * equivalent using a depth-first traversal. | ||
| + * | ||
| + * @param tree The tree to transform. | ||
| + * @param depth Indicates number of spaces for indentation. | ||
| + * | ||
| + * @return The fully transformed text. | ||
| + */ | ||
| + protected String toString( Tree<ParserRuleContext> tree, int depth ) { | ||
| + StringBuilder sb = new StringBuilder( 2048 ); | ||
| + String indent = "", newline = ""; | ||
| + | ||
| + if( beautify() ) { | ||
| + indent = getIndent( depth ); | ||
| + newline = getNewline(); | ||
| + } | ||
| + | ||
| + sb.append( indent ).append( start( tree ) ); | ||
| + | ||
| + for( Tree<ParserRuleContext> branch : tree.getBranches() ) { | ||
| + // Recurse for all the branches at this level of the tree. | ||
| + sb.append( newline ).append( toString( branch, depth + getIndentBy() ) ); | ||
| + } | ||
| + | ||
| + sb.append( newline ).append( indent ); | ||
| + | ||
| + return sb.append( stop( tree ) ).toString(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a list of contiguous payloads that are of the same class as | ||
| + * the given payload. | ||
| + */ | ||
| + private List<ParserRuleContext> getConsecutiveRules( ParserRuleContext p ) { | ||
| + List<ParserRuleContext> result = new ArrayList<ParserRuleContext>(); | ||
| + | ||
| + for( Tree<ParserRuleContext> branch : getBranches() ) { | ||
| + if( branch.isPayloadClass( p.getClass() ) ) { | ||
| + result.add( branch.getPayload() ); | ||
| + } | ||
| + else { | ||
| + // No more adjacent payloads. | ||
| + break; | ||
| + } | ||
| + } | ||
| + | ||
| + return result; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Answers whether the payload is preceded by at least one sibling. The | ||
| + * siblings must not be split by any other type of playload. | ||
| + * | ||
| + * @param payload The payload with a class that is used as a reference for | ||
| + * finding contiguous payloads. | ||
| + * | ||
| + * @return true There is at least one sibling of the same payload type | ||
| + * that is before the given payload. | ||
| + */ | ||
| + @Override | ||
| + public boolean hasPrecedingPayload( ParserRuleContext payload ) { | ||
| + return getConsecutiveRules( payload ).indexOf( payload ) > 0; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Answers whether the payload is followed by at least one sibling. The | ||
| + * siblings must not be split by any other type of playload. | ||
| + * | ||
| + * @param payload The payload with a class that is used as a reference for | ||
| + * finding contiguous payloads. | ||
| + * | ||
| + * @return true There is at least one sibling of the same payload type | ||
| + * that is after the given payload. | ||
| + */ | ||
| + @Override | ||
| + public boolean hasFollowingPayload( ParserRuleContext payload ) { | ||
| + List<ParserRuleContext> siblings = getConsecutiveRules( payload ); | ||
| + int index = siblings.indexOf( payload ); | ||
| + | ||
| + return index > -1 && (index + 1 < siblings.size()); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the transformed syntax for beginning the payload item. | ||
| + * | ||
| + * @param tree The tree that has a payload that contains a token to | ||
| + * transform into a string. | ||
| + * @return A non-null string, possibly empty. | ||
| + */ | ||
| + protected String start( Tree<ParserRuleContext> tree ) { | ||
| + return start( tree.getPayload() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method for returning the start string. | ||
| + * | ||
| + * @param payload The payload that contains a token to transform into | ||
| + * a string. | ||
| + * @return The start string for the parsed item; possibly empty, never null. | ||
| + */ | ||
| + protected String start( ParserRuleContext payload ) { | ||
| + return start( payload.getStart() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method for returning the start string. | ||
| + * | ||
| + * @param token The token to transform into a string. | ||
| + * @return The start string for the parsed item; possibly empty, never null. | ||
| + */ | ||
| + protected String start( Token token ) { | ||
| + return token.toString(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the transformed syntax for ending the payload item. | ||
| + * | ||
| + * @param tree The tree that has a payload that contains a token to | ||
| + * transform into a string. | ||
| + * @return A non-null string, possibly empty. | ||
| + */ | ||
| + protected String stop( Tree<ParserRuleContext> tree ) { | ||
| + return stop( tree.getPayload() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method for returning the stop string. | ||
| + * | ||
| + * @param payload The payload that contains a token to transform into | ||
| + * a string. | ||
| + * @return The stop string for the parsed item; possibly empty, never null. | ||
| + */ | ||
| + protected String stop( ParserRuleContext payload ) { | ||
| + return stop( payload.getStop() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method for returning the start string. | ||
| + * | ||
| + * @param token The token to transform into a string. | ||
| + * @return The start string for the parsed item; possibly empty, never null. | ||
| + */ | ||
| + protected String stop( Token token ) { | ||
| + return token.toString(); | ||
| + } | ||
| + | ||
| + protected String getNewline() { | ||
| + return System.lineSeparator(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Answers whether the transformed query should be indented with newlines. | ||
| + * | ||
| + * @return true Format the output (by default). | ||
| + */ | ||
| + protected boolean beautify() { | ||
| + // Returns the System property value. | ||
| + return Boolean.getBoolean( "beautify" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Used by subclasses to beautify the leading SQL clause (e.g., SELECT, | ||
| + * FROM, WHERE). | ||
| + * | ||
| + * @param spaces The number of spaces to include in the result. | ||
| + * @return A non-null string with 'spaces' amount of space. | ||
| + */ | ||
| + protected String getSpace( int spaces ) { | ||
| + return beautify() ? getNewline() + getIndent( spaces ) : " "; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method to return the default space amount. | ||
| + * | ||
| + * @return A non-null string with the default amount of space. | ||
| + */ | ||
| + protected String getSpace() { | ||
| + return getSpace( getIndentBy() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates a string of spaces that is spaces spaces long. | ||
| + * | ||
| + * @param spaces The number of spaces to add to the string. | ||
| + * @return A string with 'spaces' number of " " padding. | ||
| + */ | ||
| + protected String getIndent( int spaces ) { | ||
| + return CharBuffer.allocate( spaces ).toString().replace( '\0', ' ' ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the default amount of spaces. | ||
| + * | ||
| + * @return " " (by default) | ||
| + */ | ||
| + protected String getDefaultIndent() { | ||
| + return getIndent( getIndentBy() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the number of spaces to indent by. | ||
| + * | ||
| + * @return 2 (by default) | ||
| + */ | ||
| + protected int getIndentBy() { | ||
| + return INDENT; | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| + | ||
| +import org.antlr.v4.runtime.ParserRuleContext; | ||
| + | ||
| +/** | ||
| + * Transforms <b><code>table > path</code></b> into an | ||
| + * <code>XMLELEMENT</code> expression. | ||
| + */ | ||
| +public class TableMapContext extends Context { | ||
| + public TableMapContext( ParserRuleContext ctx ) { | ||
| + super( ctx ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Opens the XMLELEMENT. | ||
| + * | ||
| + * @return ",XMLELEMENT(" ... | ||
| + */ | ||
| + @Override | ||
| + public Token getStart() { | ||
| + ParserRuleContext p = getParserRuleContext(); | ||
| + QueryParser.TableMapContext ctx = (QueryParser.TableMapContext)p; | ||
| + String tableName = ctx.table(1).getText(); | ||
| + | ||
| + return new Token( String.format( ",%s", startElement( tableName ) ) ); | ||
| + } | ||
| +} | ||
| + | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| +import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| + | ||
| +import org.antlr.v4.runtime.tree.TerminalNode; | ||
| + | ||
| +/** | ||
| + */ | ||
| +public class WhereClause extends Clause { | ||
| + /** | ||
| + * Default constructor. | ||
| + */ | ||
| + public WhereClause() { | ||
| + } | ||
| + | ||
| + /** | ||
| + * Appends "<code>WHERE </code>" to the output. | ||
| + * | ||
| + * @param ctx The WHERE expression context (unused). | ||
| + */ | ||
| + public void enterWhere( QueryParser.WhereContext ctx ) { | ||
| + append( "WHERE" ).append( getNewlineIndent() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Appends "<code> AND </code>" to the output. | ||
| + * | ||
| + * @param ctx The AND expression context (unused). | ||
| + */ | ||
| + public void exitExprAnd( QueryParser.ExprAndContext ctx ) { | ||
| + append( getNewlineIndent() ).append( "AND " ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Appends "<code> OR </code>" to the output. | ||
| + * | ||
| + * @param ctx The OR expression context (unused). | ||
| + */ | ||
| + public void exitExprOr( QueryParser.ExprOrContext ctx ) { | ||
| + append( getNewlineIndent() ).append( "OR " ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Appends "<code>(</code>" to the output. | ||
| + * | ||
| + * @param ctx The parenthesis expression context (unused). | ||
| + */ | ||
| + public void enterExprParen( QueryParser.ExprParenContext ctx ) { | ||
| + append( "(" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Appends "<code>)</code>" to the output. | ||
| + * | ||
| + * @param ctx The parenthesis expression context (unused). | ||
| + */ | ||
| + public void exitExprParen( QueryParser.ExprParenContext ctx ) { | ||
| + append( ")" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Transforms an <b>rxm</b> equality comparator into ANSI SQL. | ||
| + * Note: There is probably a cleaner way to implement this monstrosity. | ||
| + * | ||
| + * @param ctx The equality comparison context. | ||
| + */ | ||
| + public void enterExprCompEqual( QueryParser.ExprCompEqualContext ctx ) { | ||
| + QueryParser.TableColumnContext tableColumn = ctx.tableColumn(); | ||
| + QueryParser.ExprSetContext set = ctx.exprSet(); | ||
| + | ||
| + // If the terminal node is null, then the equality is T_INEQ. | ||
| + TerminalNode equals = ctx.getToken( QueryParser.T_EQ, 0 ); | ||
| + | ||
| + String entity = tableColumn.getText(); | ||
| + String equalTo = (equals == null) ? "NOT " : ""; | ||
| + | ||
| + if( set == null ) { | ||
| + // Presume = or <> by default (changes to IS or IS NOT as needed). | ||
| + String comparator = ctx.getChild(1).getText(); | ||
| + QueryParser.LiteralContext literal = ctx.exprValue().literal(); | ||
| + | ||
| + if( literal == null ) { | ||
| + // No literal value to examine means it is probably a parameter. | ||
| + append( ctx.getText() ); | ||
| + } | ||
| + else { | ||
| + // Default is to presume the value literal is not the 'null' token. | ||
| + String rhs = literal.getText(); | ||
| + | ||
| + // If the "null" literal terminal node is not null, then null has | ||
| + // been found and so the value must be set to NULL and the | ||
| + // comparators changed from symbols to ANSI SQL keywords. | ||
| + if( literal.T_NULL() != null ) { | ||
| + rhs = "NULL"; | ||
| + comparator = " IS " + equalTo; | ||
| + } | ||
| + | ||
| + append( String.format( "%s%s%s", entity, comparator, rhs ) ); | ||
| + } | ||
| + } | ||
| + else { | ||
| + // Transform to ANSI SQL set notation. | ||
| + append( String.format( "%s %sIN (%s)", | ||
| + entity, | ||
| + equalTo, | ||
| + set.exprList().getText() ) ); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Appends a relational expression (less than, greater than, etc.) to the | ||
| + * output. | ||
| + * | ||
| + * @param ctx The relational expression context. | ||
| + */ | ||
| + public void enterExprCompRel( QueryParser.ExprCompRelContext ctx ) { | ||
| + append( ctx.getText() ); | ||
| + } | ||
| +} | ||
| + | ||
| Delta | 1170 lines added, 719 lines removed, 451-line increase |
|---|