| Author | Dave Jarvis <email> |
|---|---|
| Date | 2015-04-05 18:38:05 GMT-0700 |
| Commit | 7074b9fed1cb97fb09a47ad86d42098449e7a3bf |
| Parent | e6efdf1 |
| } | ||
| +// Only invoke this from the command line to create a libs directory containing | ||
| +// the dependent JAR files. | ||
| +task copyToLib(type: Copy) { | ||
| + into "lib" | ||
| + from configurations.runtime | ||
| +} | ||
| + | ||
| repositories { | ||
| mavenCentral() |
| import java.util.Iterator; | ||
| -import static java.lang.System.out; | ||
| - | ||
| /** | ||
| * Provides the ability to determine the index whereat two lists begin | ||
| * to differ in content. Both this list and the list to compare against | ||
| * must not contain null objects. Remove all nulls from both lists using | ||
| * <code>list.removeAll(Collections.singleton(null))</code> (assuming | ||
| * mutable lists). | ||
| */ | ||
| public class DivergentList<E> extends ArrayList<E> { | ||
| + @SuppressWarnings( "compatibility:-7553330276690450566" ) | ||
| + private static final long serialVersionUID = 802724795120260337L; | ||
| + | ||
| /** | ||
| * Default (empty) constructor. | ||
| } | ||
| } | ||
| - | ||
| package com.whitemagicsoftware.rxm; | ||
| -import java.util.ArrayList; | ||
| -import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.concurrent.CopyOnWriteArrayList; | ||
| 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.ParseTreeListener; | ||
| import org.antlr.v4.runtime.tree.TerminalNode; | ||
| } | ||
| + /** | ||
| + * Notifies the listeners and the context of an enter event. | ||
| + * | ||
| + * @param ctx The rule context to receive an enter event. | ||
| + */ | ||
| @Override | ||
| public void enterEveryRule( ParserRuleContext ctx ) { | ||
| getListeners().forEach( listener -> { | ||
| listener.enterEveryRule( ctx ); | ||
| ctx.enterRule( listener ); | ||
| }); | ||
| } | ||
| + /** | ||
| + * Notifies the listeners and the context of an exit event. | ||
| + * | ||
| + * @param ctx The rule context to receive an exit event. | ||
| + */ | ||
| @Override | ||
| public void exitEveryRule( ParserRuleContext ctx ) { | ||
| } | ||
| } | ||
| - | ||
| * @return The tree that was added (i.e., the tree parameter). | ||
| */ | ||
| + @SuppressWarnings( "unchecked" ) | ||
| public Tree<T> addLeaf( Tree<T> tree ) { | ||
| getBranches().add( tree ); |
| package com.whitemagicsoftware.rxm.xml; | ||
| -import com.whitemagicsoftware.rxm.Tree; | ||
| import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| */ | ||
| 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 ); | ||
| - } | ||
| + // If the column name and attribute name are the same, then the "AS" | ||
| + // clause is superfluous. | ||
| + node = column.equals( node ) ? "" : String.format( " AS \"%s\"", node ); | ||
| - return result; | ||
| + return String.format( "%s.%s%s", table, column, node ); | ||
| } | ||
| +package com.whitemagicsoftware.rxm.xml; | ||
| + | ||
| +/** | ||
| + * Isolates creation of the buffer used to write the data. | ||
| + */ | ||
| +public interface Buffered { | ||
| + default StringBuilder createBuffer() { | ||
| + return new StringBuilder( 2048 ); | ||
| + } | ||
| +} | ||
| + | ||
| * Common behaviour for various clauses. | ||
| */ | ||
| -public class Clause extends QueryBaseListener { | ||
| +public class Clause extends QueryBaseListener implements Buffered { | ||
| private static int INDENT = 2; | ||
| - private StringBuilder buffer = new StringBuilder( 2048 ); | ||
| + private StringBuilder buffer = createBuffer(); | ||
| /** |
| package com.whitemagicsoftware.rxm.xml; | ||
| -import java.util.Arrays; | ||
| -import java.util.HashSet; | ||
| -import java.util.List; | ||
| -import java.util.LinkedHashSet; | ||
| -import java.util.Set; | ||
| -import java.util.stream.Collectors; | ||
| - | ||
| import com.whitemagicsoftware.rxm.DivergentList; | ||
| -import com.whitemagicsoftware.rxm.Tree; | ||
| import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| + | ||
| +import java.util.ArrayList; | ||
| +import java.util.LinkedHashSet; | ||
| +import java.util.List; | ||
| +import java.util.Set; | ||
| import org.antlr.v4.runtime.ParserRuleContext; | ||
| -import org.antlr.v4.runtime.Vocabulary; | ||
| /** | ||
| * Transforms <b><code>column > path</code></b> into an | ||
| * <code>XMLELEMENT</code> expression. | ||
| */ | ||
| public class ColumnMapContext extends Context { | ||
| - private final static String T_SLASH = | ||
| - QueryParser.VOCABULARY.getLiteralName( QueryParser.T_SLASH ); | ||
| + // The most recent column mapping path that does not contain ellipses. | ||
| + private QueryParser.ElementPathContext explicitPath; | ||
| /** | ||
| DivergentList<String> thisList = listElementNames(); | ||
| DivergentList<String> prevList = listElementNamesPrev(); | ||
| - | ||
| - if( hasEllipsesPath() ) { | ||
| - System.out.printf( "ELLIPSES: %s%n", thisList.toString() ); | ||
| - System.out.printf( "ELLIPSES PREV: %s%n", prevList.toString() ); | ||
| - } | ||
| - | ||
| - if( hasEllipsesPath() && prevList.size() > 0 ) { | ||
| - thisList.addAll( 0, prevList.subList( 0, prevList.size() - 1 ) ); | ||
| - System.out.println( "thisList: " + thisList.toString() ); | ||
| - } | ||
| // If there are no common elements with the previous list, then this is | ||
| */ | ||
| private DivergentList<String> listElementNamesNext() { | ||
| - DivergentList<String> prev = listElementNames( getNextContext() ); | ||
| - | ||
| - if( hasEllipsesPath() ) { | ||
| - System.out.printf( "Prev: %s%n", prev.toString() ); | ||
| - } | ||
| - | ||
| - return prev; | ||
| + return listElementNames( getNextContext() ); | ||
| } | ||
| */ | ||
| protected List<QueryParser.ElementContext> element() { | ||
| - return (hasEllipsesPath() ? | ||
| + List<QueryParser.ElementContext> result = (hasEllipsesPath() ? | ||
| ellipsesPath().elementPath() : | ||
| elementPath()).element(); | ||
| + | ||
| + if( hasEllipsesPath() ) { | ||
| + result.addAll( 0, explicitPath() ); | ||
| + } | ||
| + | ||
| + return result; | ||
| + } | ||
| + | ||
| + private List<QueryParser.ElementContext> explicitPath() { | ||
| + List<QueryParser.ElementContext> result = | ||
| + new ArrayList<QueryParser.ElementContext>(); | ||
| + | ||
| + QueryParser.ElementPathContext explicitPath = getExplicitPath(); | ||
| + | ||
| + if( explicitPath != null ) { | ||
| + result.addAll( explicitPath.element() ); | ||
| + result.remove( result.size() - 1 ); | ||
| + } | ||
| + | ||
| + return result; | ||
| + } | ||
| + | ||
| + protected void setExplicitPath( QueryParser.ElementPathContext epc ) { | ||
| + this.explicitPath = epc; | ||
| + } | ||
| + | ||
| + private QueryParser.ElementPathContext getExplicitPath() { | ||
| + return this.explicitPath; | ||
| } | ||
| * Represents a wrapper class for ParserRuleContext. | ||
| */ | ||
| -public abstract class Context { | ||
| +public abstract class Context implements Buffered { | ||
| private ParserRuleContext parserRuleContext; | ||
| private Context nextContext; | ||
| protected Context getPrevContext() { | ||
| return this.prevContext; | ||
| - } | ||
| - | ||
| - protected StringBuilder createBuffer() { | ||
| - return new StringBuilder( 2048 ); | ||
| } | ||
| package com.whitemagicsoftware.rxm.xml; | ||
| -import com.whitemagicsoftware.rxm.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; | ||
| /** |
| import com.whitemagicsoftware.rxm.Tree; | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| import org.antlr.v4.runtime.ParserRuleContext; | ||
| public class SelectClause extends Clause { | ||
| private Tree<Context> tree; | ||
| + | ||
| + // The most recent column mapping path that does not contain ellipses. | ||
| + private QueryParser.ElementPathContext explicitPath; | ||
| /** | ||
| @Override | ||
| public void enterColumnMap( QueryParser.ColumnMapContext ctx ) { | ||
| - addLeaf( new ColumnMapContext( ctx, getTableName() ) ); | ||
| + ColumnMapContext cmc = new ColumnMapContext( ctx, getTableName() ); | ||
| + | ||
| + if( cmc.hasEllipsesPath() ) { | ||
| + // A column mapping always has at least one non-ellipses mapping that | ||
| + // precedes it. (See directly below.) | ||
| + cmc.setExplicitPath( getExplicitPath() ); | ||
| + } | ||
| + else { | ||
| + // A column mapping without ellipses must be stored for later use by | ||
| + // column mappings with ellipses. | ||
| + setExplicitPath( cmc.elementPath() ); | ||
| + } | ||
| + | ||
| + addLeaf( cmc ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Stores the latest column map's context that has no ellipses. | ||
| + * | ||
| + * @param epc The most recent element path that does not use ellipses. | ||
| + */ | ||
| + protected void setExplicitPath( QueryParser.ElementPathContext epc ) { | ||
| + this.explicitPath = epc; | ||
| + } | ||
| + | ||
| + private QueryParser.ElementPathContext getExplicitPath() { | ||
| + return this.explicitPath; | ||
| } | ||
| } | ||
| } | ||
| - | ||
| package com.whitemagicsoftware.rxm.xml; | ||
| +import com.whitemagicsoftware.rxm.PeekingIterator; | ||
| +import com.whitemagicsoftware.rxm.Tree; | ||
| + | ||
| import java.nio.CharBuffer; | ||
| import java.util.ArrayList; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| - | ||
| -import com.whitemagicsoftware.rxm.PeekingIterator; | ||
| -import com.whitemagicsoftware.rxm.Tree; | ||
| - | ||
| -import org.antlr.v4.runtime.Token; | ||
| /** | ||
| * Helps generate a SQL/XML SELECT clause. | ||
| */ | ||
| -public class SelectTree extends Tree<Context> { | ||
| +public class SelectTree extends Tree<Context> implements Buffered { | ||
| public static final int INDENT = 2; | ||
| */ | ||
| protected String toString( Tree<Context> tree, int depth ) { | ||
| - StringBuilder sb = new StringBuilder( 2048 ); | ||
| + StringBuilder sb = createBuffer(); | ||
| String indent = "", newline = ""; | ||
| if( beautify() ) { | ||
| indent = getIndent( depth ); | ||
| newline = getNewline(); | ||
| } | ||
| sb.append( indent ).append( start( tree ) ); | ||
| - // Immediately previous column map (if any). | ||
| - ColumnMapContext priorColumnMap = null; | ||
| List<Tree<Context>> branches = tree.getBranches(); | ||
| PeekingIterator<Tree<Context>> i = createIterator( branches.iterator() ); | ||
| else if( context instanceof ColumnMapContext ) { | ||
| if( i.hasNext() ) { | ||
| - Tree<Context> next = i.peek(); | ||
| - | ||
| - context.setNextContext( next.getPayload() ); | ||
| - context.setPrevContext( prevContext ); | ||
| + context.setNextContext( i.peek().getPayload() ); | ||
| } | ||
| + | ||
| + // Ensure that all the column maps get the previous context. This | ||
| + // helps when using the divergent list to calculate how many nested | ||
| + // elements are in common. | ||
| + context.setPrevContext( prevContext ); | ||
| } | ||
| * that is before the given payload. | ||
| */ | ||
| - public boolean hasPrecedingPayload( | ||
| - Tree<Context> tree, Context payload ) { | ||
| + public boolean hasPrecedingPayload( Tree<Context> tree, Context payload ) { | ||
| return getConsecutiveRules( tree, payload ).indexOf( payload ) > 0; | ||
| } | ||
| * that is after the given payload. | ||
| */ | ||
| - public boolean hasFollowingPayload( | ||
| - Tree<Context> tree, Context payload ) { | ||
| + public boolean hasFollowingPayload( Tree<Context> tree, Context payload ) { | ||
| List<Context> siblings = getConsecutiveRules( tree, payload ); | ||
| int index = siblings.indexOf( payload ); | ||
| } | ||
| } | ||
| - | ||
| } | ||
| - |
| package com.whitemagicsoftware.rxm.xml; | ||
| -import com.whitemagicsoftware.rxm.grammar.QueryBaseListener; | ||
| import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| } | ||
| } | ||
| - | ||
| +root > people, | ||
| +person > person, | ||
| +.first_name > name/first, | ||
| +.last_name > .../last, | ||
| + | ||
| Delta | 125 lines added, 87 lines removed, 38-line increase |
|---|