| Author | Dave Jarvis <email> |
|---|---|
| Date | 2015-03-30 23:37:27 GMT-0700 |
| Commit | fd45eae67ecb934f6c7ad13c32ec71e9f9d7b4e1 |
| Parent | b8d8020 |
| public class DivergentList<E> extends ArrayList<E> { | ||
| /** | ||
| + * Default (empty) constructor. | ||
| + */ | ||
| + public DivergentList() { | ||
| + } | ||
| + | ||
| + /** | ||
| * Constructs a list containing the elements of the | ||
| * specified collection, in the order they are returned by the |
| @Override | ||
| public String getStart() { | ||
| - DivergentList<String> list = new DivergentList<String>( elementNames() ); | ||
| + DivergentList<String> thisList = listElementNames(); | ||
| + DivergentList<String> prevList = listElementNamesPrev(); | ||
| - //System.out.println( "NEXT: " + getNextContext() ); | ||
| - //System.out.println( "PREV: " + getPrevContext() ); | ||
| + // If there are no common elements with the previous list, then this is | ||
| + // an entirely new list of elements. This logic is tricky: | ||
| + // | ||
| + // 1. diverges(...) returns the last matching index or -1 for no matches; | ||
| + // 2. the last element name in the path must be unique; therefore, | ||
| + // 3. adding 1 produces the starting index into thisList for | ||
| + // appending ,XMLELEMENTs. | ||
| + // | ||
| + // Closing the parentheses is performed in getStop(). | ||
| + int diverges = thisList.diverges( prevList ) + 1; | ||
| + StringBuilder sb = createBuffer(); | ||
| - return String.format( ",%s,%s.%s", | ||
| - startElement( path() ), | ||
| - getTableName(), | ||
| - getColumnText() | ||
| - ); | ||
| + for( String element : thisList.subList( diverges, thisList.size() ) ) { | ||
| + sb.append( ',' ).append( startElement( element ) ); | ||
| + } | ||
| + | ||
| + sb.append( String.format( ",%s.%s", getTableName(), getColumnText() ) ); | ||
| + | ||
| + return sb.toString(); | ||
| } | ||
| + /** | ||
| + * Closes the opening XMLELEMENT parentheses. | ||
| + * | ||
| + * @return ")" times the number of required closing parentheses. | ||
| + */ | ||
| @Override | ||
| public String getStop() { | ||
| - return String.format( "%s%s", | ||
| - stopElement( path() ), | ||
| - super.getStop() | ||
| - ); | ||
| + DivergentList<String> thisList = listElementNames(); | ||
| + DivergentList<String> nextList = listElementNamesNext(); | ||
| + | ||
| + // Determine the number of closing parentheses by noting how many | ||
| + // elements the next column mapping (if any) has in common with the | ||
| + // current mapping. | ||
| + return repeat( thisList.size() - thisList.diverges( nextList ) - 1, ')' ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a list of element names that can be compared against another | ||
| + * list (to ascertain where the lists diverge). | ||
| + * | ||
| + * @return A non-null, possibly empty, divergent list. | ||
| + */ | ||
| + protected DivergentList<String> listElementNames() { | ||
| + DivergentList<String> result = createDivergentList(); | ||
| + result.addAll( elementNames() ); | ||
| + return result; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method. | ||
| + */ | ||
| + private DivergentList<String> listElementNames( Context ctx ) { | ||
| + return ctx != null && ctx instanceof ColumnMapContext ? | ||
| + ((ColumnMapContext)ctx).listElementNames() : | ||
| + createDivergentList(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method. | ||
| + */ | ||
| + private DivergentList<String> listElementNamesNext() { | ||
| + return listElementNames( getNextContext() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method. | ||
| + */ | ||
| + private DivergentList<String> listElementNamesPrev() { | ||
| + return listElementNames( getPrevContext() ); | ||
| } | ||
| /** | ||
| - * 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. | ||
| + * Returns the list of elements associated with this context. This | ||
| + * is used to obtain the list of element names for the divergent lists. | ||
| */ | ||
| - protected QueryParser.PathContext path() { | ||
| - return getColumnMapContext().path(); | ||
| + protected List<QueryParser.ElementContext> element() { | ||
| + return elementPath().element(); | ||
| } | ||
| + /** | ||
| + * Helper method. | ||
| + */ | ||
| protected QueryParser.ElementPathContext elementPath() { | ||
| return path().elementPath(); | ||
| } | ||
| - protected List<QueryParser.ElementContext> element() { | ||
| - return elementPath().element(); | ||
| + /** | ||
| + * 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.nio.CharBuffer; | ||
| + | ||
| import java.util.List; | ||
| +import com.whitemagicsoftware.rxm.DivergentList; | ||
| import com.whitemagicsoftware.rxm.Tree; | ||
| import com.whitemagicsoftware.rxm.grammar.QueryParser; | ||
| /** | ||
| + * Constructs a wrapper class that provides the ParserRuleContext and | ||
| + * tree for the *Context classes. | ||
| + * | ||
| + * @param payload The parser rule context to wrap in this context. | ||
| * @param tableName The name of the table with respect to this context. | ||
| */ | ||
| public String getStop() { | ||
| return ")"; | ||
| - } | ||
| - | ||
| - /** | ||
| - * 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.ElementPathContext element = ctx.elementPath(); | ||
| - | ||
| - return 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.getText() ) ); | ||
| - sep = ","; | ||
| - } | ||
| - | ||
| - return sb.toString(); | ||
| } | ||
| /** | ||
| - * Formats the start of the XMLELEMENT call. | ||
| + * Formats the start of the XMLELEMENT call. This is used by a number | ||
| + * of subclasses to write an 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 ) { | ||
| - return 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(); | ||
| + /** | ||
| + * Returns an empty list of elements. | ||
| + * | ||
| + * @return A non-null, empty divergent list. | ||
| + */ | ||
| + protected DivergentList<String> createDivergentList() { | ||
| + return new DivergentList<String>(); | ||
| } | ||
| * | ||
| * @param tableName The new table name context (can be null). | ||
| + * @return The new table name or empty string if 'tableName' is null. | ||
| */ | ||
| public String setTableName( String tableName ) { | ||
| protected Context getPrevContext() { | ||
| return this.prevContext; | ||
| + } | ||
| + | ||
| + protected StringBuilder createBuffer() { | ||
| + return new StringBuilder( 2048 ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates a string of repeated text that is n characters long. | ||
| + * | ||
| + * @param n The number of characters in the resulting string. | ||
| + * @param ch The character to repeat. | ||
| + * @return A string padded with n characters. | ||
| + */ | ||
| + public String repeat( int n, char ch ) { | ||
| + return CharBuffer.allocate( n ).toString().replace( '\0', ch ); | ||
| } | ||
| } | ||
| } | ||
| + /** | ||
| + * Allows reusing the instance. | ||
| + */ | ||
| private void eventsDeregister() { | ||
| getProxyParseTreeListener().clear(); | ||
| } | ||
| + /** | ||
| + * Registers the initial event listeners: SELECT, FROM, and JOIN. The | ||
| + * WHERE clause listener only starts to receive events if the where | ||
| + * clause syntax event is triggered (i.e., the WHERE clause terminator is | ||
| + * encountered). | ||
| + */ | ||
| private void eventsRegister() { | ||
| receiveEvents( this ); | ||
| receiveEvents( getSelectClause() ); | ||
| receiveEvents( getFromClause() ); | ||
| receiveEvents( getJoinClause() ); | ||
| } | ||
| + /** | ||
| + * Walks the tree and issues the events. | ||
| + */ | ||
| private void eventsNotify( QueryParser.QueryContext ctx ) { | ||
| ParseTreeWalker.DEFAULT.walk( getProxyParseTreeListener(), ctx ); |
| .middle_initial2 > name/middle/initial/i2, | ||
| .middle_initial3 > name/middle/initial/i3, | ||
| -.last_name > name/last, | ||
| -.maiden_name > name/last/original/maiden, | ||
| -.flag > @flag, | ||
| +.last_name > surname, | ||
| +.maiden_name > surname/original/maiden, | ||
| .gender > gender, | ||
| +.flag > @flag, | ||
| account > account, | ||
| account.person_id +> person.id, |
| Delta | 135 lines added, 66 lines removed, 69-line increase |
|---|