Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/rxm.git

Simplified grammar to use a joinMap instead of innerMap+outerMap. Added ProxyParseTreeListener to handle dispatching events to pertinent clauses. Made WhereClause and JoinClause consistent. Might be able to simplify the SelectClause.

Author Dave Jarvis <email>
Date 2015-03-21 19:08:50 GMT-0700
Commit 8eab33addac33a15be027128f92b467521999909
Parent e3391bd
source/grammar/Query.g
************************************************************************/
-query: start statement+ where? EOF ;
+query: start statements where? EOF ;
/* Define the root, line map, and JOIN clauses. */
start : (root | module) T_COMMA ;
root : T_ROOT T_GT element ;
module: T_MODULE T_ID ;
-statement: (pop | glob | map | include) T_COMMA ;
-pop : T_POP ;
-glob : T_GLOB ;
+statements: statement+ ;
+statement : (pop | glob | map | include) T_COMMA ;
+pop : T_POP ;
+glob : T_GLOB ;
/* Map lines affect the tree depth differently. */
-map : tableMap | columnMap | attributeMap | innerMap | outerMap ;
+map : tableMap | columnMap | attributeMap | joinMap ;
tableMap : table T_GT path ;
columnMap : column T_GT path ;
attributeMap: column T_GT attribute ;
-innerMap : tableColumn T_INNER tableColumn ;
-outerMap : tableColumn T_OUTER tableColumn ;
+joinMap : tableColumn (T_INNER | T_OUTER) tableColumn ;
include : T_IMPORT T_ID ;
source/java/com/whitemagicsoftware/rxm/ProxyParseTreeListener.java
+package com.whitemagicsoftware.rxm;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeListener;
+import org.antlr.v4.runtime.tree.ErrorNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+/**
+ * Instances of this class allows multiple listeners to receive events
+ * while walking the parse tree. For example:
+ *
+ * <pre>
+ * ProxyParseTreeListener proxy = new ProxyParseTreeListener();
+ * ParseTreeListener listener1 = ... ;
+ * ParseTreeListener listener2 = ... ;
+ * proxy.add( listener1 );
+ * proxy.add( listener2 );
+ * ParseTreeWalker.DEFAULT.walk( proxy, ctx );
+ * </pre>
+ */
+public class ProxyParseTreeListener implements ParseTreeListener {
+ private List<ParseTreeListener> listeners;
+
+ /**
+ * Creates a new proxy without an empty list of listeners. Add
+ * listeners before walking the tree.
+ */
+ public ProxyParseTreeListener() {
+ // Setting the listener to null automatically instantiates a new list.
+ this( null );
+ }
+
+ /**
+ * Creates a new proxy with the given list of listeners.
+ *
+ * @param listeners A list of listerners to receive events.
+ */
+ public ProxyParseTreeListener( List<ParseTreeListener> listeners ) {
+ setListeners( listeners );
+ }
+
+ @Override
+ public void enterEveryRule( ParserRuleContext ctx ) {
+ Iterator<ParseTreeListener> i = iterator();
+
+ while( i.hasNext() ) {
+ ParseTreeListener listener = i.next();
+ listener.enterEveryRule( ctx );
+ ctx.enterRule( listener );
+ }
+ }
+
+ @Override
+ public void exitEveryRule( ParserRuleContext ctx ) {
+ Iterator<ParseTreeListener> i = iterator();
+
+ while( i.hasNext() ) {
+ ParseTreeListener listener = i.next();
+ ctx.exitRule( listener );
+ listener.exitEveryRule( ctx );
+ }
+ }
+
+ @Override
+ public void visitErrorNode( ErrorNode node ) {
+ Iterator<ParseTreeListener> i = iterator();
+
+ while( i.hasNext() ) {
+ ParseTreeListener listener = i.next();
+ listener.visitErrorNode( node );
+ }
+ }
+
+ @Override
+ public void visitTerminal( TerminalNode node ) {
+ Iterator<ParseTreeListener> i = iterator();
+
+ while( i.hasNext() ) {
+ ParseTreeListener listener = i.next();
+ listener.visitTerminal( node );
+ }
+ }
+
+ /**
+ * Adds the given listener to the list of event notification recipients.
+ *
+ * @param listener A listener to begin receiving events.
+ */
+ public void add( ParseTreeListener listener ) {
+ getListeners().add( listener );
+ }
+
+ /**
+ * Removes the given listener to the list of event notification recipients.
+ *
+ * @param listener A listener to stop receiving events.
+ * @return false The listener was not registered to receive events.
+ */
+ public boolean remove( ParseTreeListener listener ) {
+ return getListeners().remove( listener );
+ }
+
+ /**
+ * Returns an iterator of a copy of the current list. This protects
+ * against concurrent modifications to the list.
+ *
+ * @return A non-null, possibly empty, list of ParseTreeListeners that
+ * will receive events.
+ */
+ private Iterator<ParseTreeListener> iterator() {
+ List<ParseTreeListener> list = createParseTreeListenerList();
+ list.addAll( getListeners() );
+ return list.iterator();
+ }
+
+ /**
+ * Returns the list of listeners.
+ *
+ * @return The list of listeners to receive tree walking events.
+ */
+ private List<ParseTreeListener> getListeners() {
+ return this.listeners;
+ }
+
+ /**
+ * Changes the list of listeners to receive events. If the given list of
+ * listeners is null, an empty list will be created.
+ *
+ * @param listeners A list of listeners to receive tree walking
+ * events.
+ */
+ public void setListeners( List<ParseTreeListener> listeners ) {
+ if( listeners == null ) {
+ listeners = createParseTreeListenerList();
+ }
+
+ this.listeners = listeners;
+ }
+
+ protected List<ParseTreeListener> createParseTreeListenerList() {
+ return new ArrayList<ParseTreeListener>();
+ }
+}
+
source/java/com/whitemagicsoftware/rxm/QueryBuilder.java
import com.whitemagicsoftware.rxm.tree.Tree;
import com.whitemagicsoftware.rxm.tree.xml.SelectClauseTree;
-import com.whitemagicsoftware.rxm.tree.xml.JoinClauseList;
+import com.whitemagicsoftware.rxm.tree.xml.JoinClause;
import com.whitemagicsoftware.rxm.tree.xml.WhereClause;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
/** Current context. */
private Tree<Payload> selectClauseTree;
-
- /** List of inner and outer joins (tree used as flat hierarchy). */
- private Tree<Payload> joinClauseList;
+
+ private JoinClause joinClause = new JoinClause();
private WhereClause whereClause = new WhereClause();
+
+ private ProxyParseTreeListener proxy = new ProxyParseTreeListener();
/**
QueryLexer lexer = new QueryLexer( new ANTLRInputStream( rxm ) );
CommonTokenStream tokens = new CommonTokenStream( lexer );
+ QueryParser.QueryContext ctx = (new QueryParser( tokens )).query();
- QueryParser parser = new QueryParser( tokens );
- QueryParser.QueryContext ctx = parser.query();
+ receiveEvents( this );
+ receiveEvents( getJoinClause() );
+ notifyEvents( ctx );
+ }
- ParseTreeWalker walker = new ParseTreeWalker();
- walker.walk( this, ctx );
+ private void notifyEvents( QueryParser.QueryContext ctx ) {
+ ParseTreeWalker.DEFAULT.walk( getProxyParseTreeListener(), ctx );
+ }
+
+ private void receiveEvents( ParseTreeListener listener ) {
+ getProxyParseTreeListener().add( listener );
+ }
+
+ private void rescindEvents( ParseTreeListener listener ) {
+ getProxyParseTreeListener().remove( listener );
}
}
- @Override
- public synchronized void enterInnerMap( QueryParser.InnerMapContext ctx ) {
- addJoinClause( ctx );
- }
-
- @Override
- public synchronized void enterOuterMap( QueryParser.OuterMapContext ctx ) {
- addJoinClause( ctx );
- }
-
- @Override
- public synchronized void enterWhere( QueryParser.WhereContext ctx ) {
- getWhereClause().enterWhere( ctx );
- }
-
- @Override
- public void exitExprOr( QueryParser.ExprOrContext ctx ) {
- getWhereClause().exitExprOr( ctx );
- }
-
- @Override
- public void exitExprAnd( QueryParser.ExprAndContext ctx ) {
- getWhereClause().exitExprAnd( ctx );
- }
-
- @Override
- public synchronized void enterExprParen( QueryParser.ExprParenContext ctx ) {
- getWhereClause().enterExprParen( ctx );
- }
-
- @Override
- public synchronized void exitExprParen( QueryParser.ExprParenContext ctx ) {
- getWhereClause().exitExprParen( ctx );
- }
-
- @Override
- public synchronized void enterExprCompRel(
- QueryParser.ExprCompRelContext ctx ) {
- getWhereClause().enterExprCompRel( ctx );
- }
-
+ /**
+ * The where clause begins after all the statements. The where clause
+ * is added to the listeners at this point to receive the enterWhere
+ * and subsequent events. Listening at enterWhere would be too late.
+ */
@Override
- public synchronized void enterExprCompEqual(
- QueryParser.ExprCompEqualContext ctx ) {
- getWhereClause().enterExprCompEqual( ctx );
+ public void exitStatements(
+ QueryParser.StatementsContext ctx ) {
+ receiveEvents( getWhereClause() );
+ rescindEvents( getJoinClause() );
}
/**
* Invoked when there are no more <b>rxm</b> tokens to parse.
*
* @param ctx Indicates that the query has been parsed.
*/
@Override
- public synchronized void exitQuery( QueryParser.QueryContext ctx ) {
+ public void exitQuery( QueryParser.QueryContext ctx ) {
String select = getSelectClause();
String from = getFromClause();
- String join = getJoinClause();
+ String join = getJoinClause().toString();
String where = getWhereClause().toString();
*
* @return The FROM portion of a SQL statement.
- */
- private String getJoinClause() {
- return getJoinClauseList().toString();
- }
-
- /**
- * Adds a new leaf, representing a JOIN clause, to the from clause list.
- *
- * @param ctx The INNER/OUTER JOIN expression to add to the list.
*/
- private void addJoinClause( ParserRuleContext ctx ) {
- getJoinClauseList().addLeaf( createJoinClauseList( ctx ) );
+ private JoinClause getJoinClause() {
+ return this.joinClause;
}
return sct;
- }
-
- /**
- * Changes where the next mapped items will be attached.
- */
- private void setJoinClauseList( Tree<Payload> joinClauseList ) {
- this.joinClauseList = joinClauseList;
- }
-
- /**
- * Lazily-initializes the from clause list.
- *
- * @return The list that will contain the query's FROM clause.
- */
- private Tree<Payload> getJoinClauseList() {
- Tree<Payload> list = this.joinClauseList;
-
- if( list == null ) {
- setJoinClauseList( list = createJoinClauseList( null ) );
- }
-
- return list;
}
- /**
- * Creates a new JoinClauseList instance containing the given parser
- * rule context. The resulting tree should only have leaves added
- * to the root, never branching beyond a flat hierarchy (one-level deep).
- *
- * @return A new JoinClauseList instance, never null, containing a payload
- * with the parser rule context.
- */
- private Tree<Payload> createJoinClauseList( ParserRuleContext ctx ) {
- return new JoinClauseList<Payload>( new Payload( ctx ) );
+ private ProxyParseTreeListener getProxyParseTreeListener() {
+ return this.proxy;
}
}
source/java/com/whitemagicsoftware/rxm/tree/xml/Clause.java
+package com.whitemagicsoftware.rxm.tree.xml;
+
+import java.nio.CharBuffer;
+
+import com.whitemagicsoftware.rxm.grammar.QueryBaseListener;
+import com.whitemagicsoftware.rxm.grammar.QueryParser;
+
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+/**
+ * Common behaviour for various clauses.
+ */
+public class Clause extends QueryBaseListener {
+ private static int INDENT = 2;
+
+ private StringBuilder buffer = new StringBuilder( 2048 );
+
+ /**
+ */
+ 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 default amount of spaces.
+ *
+ * @return " " (by default)
+ */
+ protected String getDefaultIndent() {
+ return getIndent( getIndentBy() );
+ }
+
+ /**
+ * 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 number of spaces to indent by.
+ *
+ * @return 2 (by default)
+ */
+ protected int getIndentBy() {
+ return INDENT;
+ }
+}
+
source/java/com/whitemagicsoftware/rxm/tree/xml/InnerMapContext.java
-package com.whitemagicsoftware.rxm.tree.xml;
-
-import com.whitemagicsoftware.rxm.grammar.QueryParser;
-import com.whitemagicsoftware.rxm.tree.Payload;
-import com.whitemagicsoftware.rxm.tree.Token;
-import com.whitemagicsoftware.rxm.tree.Tree;
-
-import org.antlr.v4.runtime.ParserRuleContext;
-
-/**
- * Transforms <b><code>table.column +&gt; table.column</code></b> into a SQL
- * <code>INNER JOIN</code> expression.
- */
-public class InnerMapContext extends JoinMapContext {
- /**
- * Default constructor (calls super).
- *
- * @param ctx The payload that associates the parser rule context with
- * the abstract syntax tree.
- */
- public InnerMapContext( Payload ctx ) {
- super( ctx );
- }
-
- /**
- * Returns the JOIN qualifier keyword.
- *
- * @return "INNER";
- */
- @Override
- public String getJoinType() {
- return "INNER";
- }
-
- @Override
- protected QueryParser.TableColumnContext tableColumn( int i ) {
- return ((QueryParser.InnerMapContext)getParserRuleContext()).tableColumn(i);
- }
-}
-
source/java/com/whitemagicsoftware/rxm/tree/xml/JoinClause.java
+package com.whitemagicsoftware.rxm.tree.xml;
+
+import com.whitemagicsoftware.rxm.tree.Payload;
+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 +&gt; table.column</code></b> into a SQL
+ * <code>INNER JOIN</code> expression; similarly, transforms the operand
+ * <code>-&gt;</code> into <code>OUTER JOIN</code>.
+ */
+public class JoinClause extends Clause {
+ public JoinClause() {
+ }
+
+ @Override
+ public void enterJoinMap( QueryParser.JoinMapContext ctx ) {
+ QueryParser.TableColumnContext
+ lhs = ctx.tableColumn( 0 ),
+ rhs = ctx.tableColumn( 0 );
+
+ String lhsTableName = lhs.table().getText();
+ String rhsTableName = rhs.table().getText();
+
+ append( String.format( "%s JOIN %s %s ON%s%s = %s%s",
+ ctx.T_OUTER() == null ? "INNER" : "OUTER",
+ lhsTableName,
+ lhsTableName,
+ getNewlineIndent(),
+ lhsTableName,
+ rhsTableName,
+ getNewline() )
+ );
+ }
+}
+
source/java/com/whitemagicsoftware/rxm/tree/xml/JoinMapContext.java
-package com.whitemagicsoftware.rxm.tree.xml;
-
-import com.whitemagicsoftware.rxm.grammar.QueryParser;
-import com.whitemagicsoftware.rxm.tree.Payload;
-import com.whitemagicsoftware.rxm.tree.Token;
-
-import org.antlr.v4.runtime.ParserRuleContext;
-
-/**
- * Superclass for common behaviour between INNER and OUTER JOIN clauses.
- */
-public abstract class JoinMapContext extends PayloadParserRuleContext {
- /**
- * Default constructor (calls super).
- *
- * @param initPayload The payload that associates the parser rule context
- * with the abstract syntax tree.
- */
- public JoinMapContext( Payload initPayload ) {
- super( initPayload );
- }
-
- public abstract String getJoinType();
-
- /**
- * Returns the starting text for an OUTER JOIN clause.
- */
- @Override
- public Token getStart() {
- return new Token( String.format( "%s JOIN %s %s ON",
- getJoinType(),
- getTableName(),
- getTableName() )
- );
- }
-
- /**
- * Returns the ending text for an OUTER JOIN clause.
- */
- @Override
- public Token getStop() {
- return new Token(
- String.format( "%s = %s",
- tableColumn( 0 ).getText(),
- tableColumn( 1 ).getText()
- )
- );
- }
-
- private String getTableName() {
- return table().getText();
- }
-
- /**
- * Returns the left-hand-side (LHS) table, used for <code>JOIN * ON</code>.
- *
- * @return The <code>*</code> value in the aforementioned JOIN.
- */
- protected QueryParser.TableContext table() {
- return tableColumn( 0 ).table();
- }
-
- /**
- * Returns the left-hand-side (LHS) or right-hand-side (RHS) table column
- * context.
- *
- * @param i Pass 0 for the LHS, pass 1 for the RHS.
- * @return The TableColumnContext for the given index.
- */
- protected abstract QueryParser.TableColumnContext tableColumn( int i );
-}
-
source/java/com/whitemagicsoftware/rxm/tree/xml/OuterMapContext.java
-package com.whitemagicsoftware.rxm.tree.xml;
-
-import com.whitemagicsoftware.rxm.grammar.QueryParser;
-import com.whitemagicsoftware.rxm.tree.Payload;
-import com.whitemagicsoftware.rxm.tree.Token;
-import com.whitemagicsoftware.rxm.tree.Tree;
-
-import org.antlr.v4.runtime.ParserRuleContext;
-
-/**
- * Transforms <b><code>table.column -&gt; table.column</code></b> into a SQL
- * <code>OUTER JOIN</code> expression.
- */
-public class OuterMapContext extends JoinMapContext {
- /**
- * Default constructor (calls super).
- *
- * @param ctx The payload that associates the parser rule context with
- * the abstract syntax tree.
- */
- public OuterMapContext( Payload ctx ) {
- super( ctx );
- }
-
- /**
- * Returns the JOIN qualifier keyword.
- *
- * @return "OUTER";
- */
- @Override
- public String getJoinType() {
- return "OUTER";
- }
-
- @Override
- protected QueryParser.TableColumnContext tableColumn( int i ) {
- return ((QueryParser.OuterMapContext)getParserRuleContext()).tableColumn(i);
- }
-}
-
source/java/com/whitemagicsoftware/rxm/tree/xml/PayloadParserRuleContext.java
* Formats the start of the XMLELEMENT call.
*
- * @param name The name assigned to the element.
+ * @param ctx The name assigned to the element.
*
* @return The text used for SQL/XML XMLELEMENT calls.
source/java/com/whitemagicsoftware/rxm/tree/xml/WhereClause.java
/**
*/
-public class WhereClause {
- private StringBuilder buffer = new StringBuilder( 2048 );
-
- /**
- */
+public class WhereClause extends Clause {
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( "WHERE" ).append( getNewlineIndent() );
}
/**
* Appends "<code> AND </code>" to the output.
*
* @param ctx The AND expression context (unused).
*/
public void exitExprAnd( QueryParser.ExprAndContext ctx ) {
- append( " AND " );
+ 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( " OR " );
+ append( getNewlineIndent() ).append( "OR " );
}
public void enterExprCompRel( QueryParser.ExprCompRelContext ctx ) {
append( ctx.getText() );
- }
-
- /**
- * Appends a given string to the buffer.
- *
- * @param s The string to append.
- */
- private void append( String s ) {
- getBuffer().append( s );
- }
-
- /**
- * Returns the buffer used for building the transformed output.
- *
- * @return A non-null string builder, possibly empty.
- */
- private StringBuilder getBuffer() {
- return this.buffer;
- }
-
- /**
- * 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();
}
}
Delta 352 lines added, 294 lines removed, 58-line increase