{"action":"created","comment":{"url":"https://api.github.com/repos/aodn/netcdf_filters/pulls/comments/28481337","id":28481337,"diff_hunk":"@@ -0,0 +1,1476 @@\n+\n+\n+package au.org.emii.ncdfgenerator;\n+\n+import java.io.BufferedInputStream;\n+import java.io.ByteArrayInputStream;\n+import java.io.InputStream ;\n+import java.io.OutputStream ;\n+import java.io.IOException;\n+\n+import java.nio.charset.Charset;\n+\n+import java.util.ArrayList;\n+import java.util.Date;\n+import java.util.Iterator;\n+import java.util.HashMap;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.regex.Pattern ;\n+import java.util.regex.Matcher;\n+import java.util.AbstractMap.SimpleImmutableEntry;\n+\n+import java.sql.*;\n+\n+import java.text.SimpleDateFormat;\n+\n+import ucar.nc2.NetcdfFileWriteable;\n+import ucar.nc2.Dimension;\n+import ucar.ma2.DataType;\n+import ucar.ma2.Array;\n+import ucar.ma2.ArrayDouble;\n+import ucar.ma2.Index;\n+\n+import org.w3c.dom.Document;\n+import org.w3c.dom.Node;\n+import org.w3c.dom.NamedNodeMap;\n+import org.w3c.dom.NodeList;\n+\n+import java.nio.charset.StandardCharsets;\n+import javax.xml.parsers.DocumentBuilderFactory;\n+\n+\n+\n+class NcdfGeneratorException extends Exception\n+{\n+    public NcdfGeneratorException(String message)\n+\t{\n+        super(message);\n+    }\n+}\n+\n+\n+\n+interface IExprVisitor\n+{\n+\tpublic void visit( ExprInteger expr )throws Exception;\n+\tpublic void visit( ExprProc expr ) throws Exception;\n+\tpublic void visit( ExprLiteral expr ) throws Exception;\n+\tpublic void visit( ExprSymbol expr ) throws Exception;\n+\tpublic void visit( ExprTimestamp expr )throws Exception;\n+}\n+\n+\n+interface IExpression\n+{\n+\tpublic int getPosition() ;\n+\tpublic void accept( IExprVisitor v ) throws Exception ;\n+}\n+\n+\n+class ExprSymbol implements IExpression\n+{\n+\tpublic ExprSymbol( int pos, String value)\n+\t{\n+\t\tthis.pos = pos;\n+\t\tthis.value = value;\n+\t}\n+\n+\tpublic int getPosition() { return pos; }\n+\tpublic void accept( IExprVisitor v )  throws Exception\n+\t{ v.visit( this); }\n+\n+\tfinal int pos;\n+\tfinal String value;\n+}\n+\n+\n+class ExprInteger implements IExpression\n+{\n+\tpublic ExprInteger( int pos, int value)\n+\t{\n+\t\tthis.pos = pos;\n+\t\tthis.value = value;\n+\t}\n+\n+\tpublic int getPosition() { return pos; }\n+\tpublic void accept( IExprVisitor v ) throws Exception\n+\t{ v.visit( this); }\n+\n+\tfinal int pos;\n+\tfinal int value;\n+}\n+\n+\n+class ExprTimestamp implements IExpression\n+{\n+\tpublic ExprTimestamp( int pos, Timestamp value)\n+\t{\n+\t\tthis.pos = pos;\n+\t\tthis.value = value;\n+\t}\n+\n+\tpublic int getPosition() { return pos; }\n+\tpublic void accept( IExprVisitor v ) throws Exception\n+\t{ v.visit( this); }\n+\n+\tfinal int pos;\n+\tfinal Timestamp value;\n+}\n+\n+\n+class ExprLiteral implements IExpression\n+{\n+\tpublic ExprLiteral( int pos, String value)\n+\t{\n+\t\tthis.pos = pos;\n+\t\tthis.value = value;\n+\t}\n+\n+\tpublic int getPosition() { return pos; }\n+\tpublic void accept( IExprVisitor v ) throws Exception\n+\t{ v.visit( this); }\n+\tfinal int pos;\n+\tfinal String value;\n+}\n+\n+\n+class ExprProc implements IExpression\n+{\n+\tpublic ExprProc( int pos, String symbol, ArrayList<IExpression> children  )\n+\t{\n+\t\tthis.pos = pos;\n+\t\tthis.symbol = symbol;\n+\t\tthis.children = children;\n+\t}\n+\n+\tpublic int getPosition() { return pos; }\n+\tpublic void accept( IExprVisitor v )\n+\tthrows Exception { v.visit( this); }\n+\n+\tfinal int\t\tpos;\n+\tfinal String symbol;\n+\tfinal ArrayList<IExpression> children;\n+}\n+\n+\n+interface IExprParser\n+{\n+\tpublic IExpression parseExpression(String s, int pos);\n+\n+}\n+\n+\n+class ExprParser implements IExprParser\n+{\n+\t// the input source is actually constant. while the pos needs to be held\n+\t// on the stack\n+\t// potentially we should keep the buffer state around...\n+\n+\n+\t// | Int\n+\t// | f '(' expr_list ')'   f\n+\t// | expr_list =\n+\t//\t\t\texpr\n+\t// | '(' expr, expr ')'   tuple\n+\n+\t// TODO we should check that we have matched the string entirely\n+\t// with whitespace at the end... parseEOF or similar?\n+\n+\tpublic ExprParser() {\n+\n+\t\tdf = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss'Z'\");\n+\t}\n+\n+\t// should pass this as a constructor dependency\n+\tfinal SimpleDateFormat df;\n+\n+\tpublic IExpression parseExpression(String s, int pos)\n+\t{\n+\t\t// advance whitespace\n+\t\twhile(Character.isSpaceChar(s.charAt(pos))) {\n+\t\t\t++pos;\n+\t\t}\n+\t\t// timestamp\n+\t\tIExpression expr = parseTimestamp(s, pos);\n+\t\tif(expr != null) {\n+\t\t\treturn expr;\n+\t\t}\n+\n+\t\t// integer\n+\t\texpr = parseInt(s, pos);\n+\t\tif(expr != null)\n+\t\t\treturn expr;\n+\t\t// literal\n+\t\texpr = parseLiteral(s, pos);\n+\t\tif(expr != null)\n+\t\t\treturn expr;\n+\t\texpr = parseSymbol( s, pos );\n+\t\tif(expr != null)\n+\t\t\treturn expr;\n+\t\t// proc\n+\t\texpr = parseProc(s, pos);\n+\t\tif(expr != null)\n+\t\t\treturn expr;\n+\t\treturn null;\n+\t}\n+\n+\tprivate ExprProc parseProc(String s, int pos)\n+\t{\n+\t\tString symbol = null;\n+\n+\t\tif(s.charAt(pos) != '(')\n+\t\t\treturn null;\n+\t\t++pos;\n+\n+\t\t// advance whitespace\n+\t\twhile(Character.isSpaceChar(s.charAt(pos))) {\n+\t\t\t++pos;\n+\t\t}\n+\n+\t\t// symbol\n+\t\tif(Character.isLetter(s.charAt(pos)) || s.charAt(pos) == '_' ) {\n+\t\t\tStringBuilder b = new StringBuilder();\n+\t\t\twhile(Character.isLetter(s.charAt(pos))\n+\t\t\t\t|| Character.isDigit(s.charAt(pos))\n+\t\t\t\t|| s.charAt(pos) == '_') {\n+\t\t\t\tb.append(s.charAt(pos));\n+\t\t\t\t++pos;\n+\t\t\t}\n+\t\t\tsymbol = b.toString();\n+\t\t}\n+\n+\t\t// children\n+\t\tArrayList<IExpression> children = new ArrayList<IExpression>();\n+\t\tIExpression child = null;\n+\t\tdo {\n+\t\t\tchild = parseExpression( s, pos);\n+\t\t\tif( child != null ) {\n+\t\t\t\tchildren.add( child);\n+\t\t\t\tpos = child.getPosition();\n+\t\t\t}\n+\t\t} while(child != null);\n+\n+\t\t// advance whitespace\n+\t\twhile(Character.isSpaceChar(s.charAt(pos))) {\n+\t\t\t++pos;\n+\t\t}\n+\n+\t\tif(s.charAt(pos) != ')')\n+\t\t\treturn null;\n+\t\t++pos;\n+\n+\t\treturn new ExprProc ( pos, symbol, children );\n+\t}\n+\n+\tprivate ExprSymbol parseSymbol( String s, int pos)\n+\t{\n+\t\t// atom....\n+\t\t// symbol\n+\t\tint pos2 = pos;\n+\t\tif(Character.isLetter(s.charAt(pos2)) || s.charAt(pos2) == '_' ) {\n+\t\t\twhile(Character.isLetter(s.charAt(pos2))\n+\t\t\t\t|| Character.isDigit(s.charAt(pos2))\n+\t\t\t\t|| s.charAt(pos2) == '_') {\n+\t\t\t\t++pos2;\n+\t\t\t}\n+\t\t\treturn new ExprSymbol( pos2, s.substring(pos,pos2));\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\n+\tprivate ExprTimestamp parseTimestamp( String s, int pos )\n+\t{\n+\t\t// eg. if it looks like a date\n+\t\tint pos2 = pos;\n+\t\twhile(Character.isDigit(s.charAt(pos2))\n+\t\t\t|| s.charAt(pos2) == '-'\n+\t\t\t|| s.charAt(pos2) == ':'\n+\t\t\t|| s.charAt(pos2) == 'Z'\n+\t\t\t|| s.charAt(pos2) == 'T'\n+\t\t) ++pos2;\n+\n+\t\tif(pos != pos2) {\n+\t\t\ttry {\n+\t\t\t\tString x = s.substring( pos, pos2);\n+\t\t\t\tTimestamp d = new java.sql.Timestamp(df.parse(x).getTime());\n+\t\t\t\treturn new ExprTimestamp( pos2, d);\n+\t\t\t} catch( Exception e ) {\n+\t\t\t}\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\tprivate ExprInteger parseInt( String s, int pos )\n+\t{\n+\t\tint pos2 = pos;\n+\t\twhile(Character.isDigit(s.charAt(pos2)))\n+\t\t\t++pos2;\n+\n+\t\tif( pos != pos2) {\n+\t\t\tint value = Integer.parseInt(s.substring(pos, pos2));\n+\t\t\treturn new ExprInteger(pos2, value);\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\tprivate ExprLiteral parseLiteral( String s, int pos )\n+\t{\n+\t\t// TODO pos2\n+\t\tint pos2 = pos;\n+\t\t// ignore escaping for the moment\n+\t\tif(s.charAt(pos2) != '\\'')\n+\t\t\treturn null;\n+\t\t++pos2;\n+\n+\t\twhile(s.charAt(pos2) != '\\'')\n+\t\t\t++pos2;\n+\n+\t\t++pos2;\n+\t\treturn new ExprLiteral(pos, s.substring( pos + 1, pos2 - 1));\n+\t}\n+}\n+\n+\n+class PrettyPrinterVisitor implements IExprVisitor\n+{\n+\n+\tPrettyPrinterVisitor( OutputStream os)\n+\t{\n+\t\tthis.os = os;\n+\t}\n+\n+\tOutputStream\tos;\n+\n+\tprivate void write( String s )\n+\t\tthrows IOException\n+\t{\n+\t\tos.write( s.getBytes(Charset.forName(\"UTF-8\")));\n+\t}\n+\n+\tpublic void visit( ExprSymbol expr ) throws Exception\n+\t{\n+\t\twrite( \"Symbol:\" + expr.value );\n+\t}\n+\n+\tpublic void visit(  ExprInteger expr ) throws Exception\n+\t{\n+\t\twrite( \"Integer:\" + expr.value );\n+\t}\n+\n+\tpublic void visit( ExprTimestamp expr ) throws Exception\n+\t{\n+\t\twrite( \"Timestamp:\" + expr.value );\n+\t}\n+\n+\tpublic void visit(  ExprLiteral expr ) throws Exception\n+\t{\n+\t\twrite( \"Literal:\" + expr.value );\n+\t}\n+\n+\tpublic void visit( ExprProc expr ) throws Exception\n+\t{\n+\t\twrite( \"(\" + expr.symbol + \" \" );\n+\t\tfor( IExpression child : expr.children ) {\n+\t\t\tchild.accept(this);\n+\t\t\twrite(  \" \");\n+\t\t}\n+\t\twrite( \")\" );\n+\t}\n+}\n+\n+\n+class PGDialectSelectionGenerator implements IExprVisitor\n+{\n+\tStringBuilder b;\n+\tSimpleDateFormat df;\n+\n+\n+\tpublic PGDialectSelectionGenerator( StringBuilder b )\n+\t{\n+\t\tthis.b = b;\n+\t\tthis.df = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss'Z'\");\n+\t}\n+\n+\t// think our naming is incorrect\n+\tpublic void visit( ExprInteger expr )\n+\t{\n+\t\t// This should actually emit a '?' and load the value into the sql parameter list\n+\t\t// to avoid sql injection\n+\t\tb.append( expr.value );\n+\t}\n+\n+\tpublic void visit( ExprTimestamp expr )\n+\t{\n+\t\tb.append( \"'\" + df.format(expr.value ) + \"'\" );\n+\t}\n+\n+\tpublic void visit( ExprLiteral expr )\n+\t{\n+\t\tb.append(\"'\"+ expr.value + \"'\");\n+\t}\n+\n+\tpublic void visit( ExprSymbol expr )\n+\t{\n+\t\t// must quote field to enforce full case handling\n+\t\tb.append('\\\"' + expr.value + '\\\"' );\n+\t}\n+\n+\tpublic void visit( ExprProc expr ) throws Exception\n+\t{\n+\t\tString symbol = expr.symbol;\n+\n+\t\tif(symbol.equals(\"equals\")) {\n+\t\t\temitInfixSqlExpr( \"=\", expr );\n+\t\t}\n+\t\telse if(symbol.equals(\"gt\")) {\n+\t\t\temitInfixSqlExpr( \">\", expr );\n+\t\t}\n+\t\telse if(symbol.equals(\"lt\")) {\n+\t\t\temitInfixSqlExpr( \"<\", expr );\n+\t\t}\n+\t\telse if(symbol.equals(\"geq\")) {\n+\t\t\temitInfixSqlExpr( \">=\", expr );\n+\t\t}\n+\t\telse if( symbol.equals(\"and\")\n+\t\t\t|| symbol.equals(\"or\")\n+\t\t\t) {\n+\t\t\temitInfixSqlExpr( symbol, expr );\n+\t\t}\n+\t\telse {\n+\t\t\tthrow new NcdfGeneratorException( \"Unrecognized proc expression symbol '\" + symbol + \"'\" );\n+\t\t}\n+\t}\n+\n+\tpublic void emitInfixSqlExpr( String op, ExprProc expr ) throws Exception\n+\t{\n+\t\t// if expansion is done in order we may be ok,....\n+\t\tb.append('(');\n+\t\texpr.children.get(0).accept(this);\n+\t\tb.append(' ');\n+\t\tb.append(op);\n+\t\tb.append(' ');\n+\t\texpr.children.get(1).accept(this);\n+\t\tb.append(')');\n+\t}\n+}\n+\n+\n+interface IDialectTranslate\n+{\n+\tpublic String process( IExpression expr ) throws Exception ;\n+}\n+\n+\n+class PGDialectTranslate implements IDialectTranslate\n+{\n+\t// we have to have something to instantiate the specific visitor\n+\tpublic PGDialectTranslate( )\n+\t{\n+\t\t// Ideally we should pass the PGDialectSelectionGenerator on the constructor\n+\t\t// however the issue is that it needs the stringBuilder to assemble\n+\t\t// , which would require creating a property setter.\n+\t\t; // this.visitor = visitor;\n+\t}\n+\n+\tpublic String process( IExpression expr ) throws Exception\n+\t{\n+\t\tStringBuilder b = new StringBuilder();\n+\t\texpr.accept( new PGDialectSelectionGenerator( b));\n+\n+\t\treturn b.toString();\n+\t}\n+}\n+\n+\n+interface IValueEncoder\n+{\n+\t// Netcdf value encoder from java/sql types\n+\n+\tpublic void encode( Array array, int ima, Object value ) throws NcdfGeneratorException;\n+\tpublic void prepare( Map<String, String> attributes ) throws NcdfGeneratorException;\n+\tpublic DataType targetType();\n+}\n+\n+\n+class TimestampValueEncoder implements IValueEncoder\n+{\n+\tTimestampValueEncoder()\n+\t{\n+\t\t// all the date attribute parsing slows the code a lot so calculate once at init .\n+\t\tthis.epoch = 0;\n+\t\tthis.unit = null;\n+\t\tthis.fill = 1234;\n+\t}\n+\n+\t// need a date unit...\n+\tlong epoch;  // in seconds\n+\tString unit; // seconds, days\n+\tfloat fill;\n+\n+\tpublic DataType targetType()\n+\t{\n+\t\treturn DataType.FLOAT;\n+\t}\n+\n+\tpublic void prepare( Map<String, String> attributes ) throws NcdfGeneratorException\n+\t{\n+\t\tMatcher m = Pattern.compile(\"([a-zA-Z]*)[ ]*since[ ]*(.*)\").matcher( attributes.get(\"units\") );\n+\t\tif(!m.find())\n+\t\t{\n+\t\t\tthrow new NcdfGeneratorException( \"couldn't parse attribute date\");\n+\t\t}\n+\t\tunit = m.group(1);\n+\t\tString epochString = m.group(2);\n+\t\ttry {\n+\t\t\tSimpleDateFormat df = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss Z\");\n+\t\t\tDate ts = df.parse(epochString);\n+\t\t\tepoch = (Long) ts.getTime() / 1000 ;\n+\t\t} catch( Exception e )\n+\t\t{\n+\t\t\tthrow new NcdfGeneratorException( \"couldn't extract timestamp '\" + epochString + \"' \" + e.getMessage()  );\n+\t\t}\n+\n+\t\tfill = Float.valueOf( attributes.get( \"_FillValue\" )).floatValue();\n+\t}\n+\n+\tpublic void encode( Array array, int ima,  Object value ) throws NcdfGeneratorException\n+\t{\n+\n+\t\tif( value == null) {\n+\t\t\tarray.setFloat( ima, fill );\n+\t\t}\n+\t\telse if( value instanceof java.sql.Timestamp ) {\n+\t\t\tlong seconds =  ((java.sql.Timestamp)value).getTime() / 1000  ;\n+\t\t\tlong ret = seconds - epoch;\n+\t\t\tif( unit.equals(\"days\"))\n+\t\t\t\tret /= 86400;\n+\t\t\telse if( unit.equals(\"minutes\"))\n+\t\t\t\tret /= 1440;\n+\t\t\telse if ( unit.equals(\"seconds\"))\n+\t\t\t\t;\n+\t\t\telse\n+\t\t\t\tthrow new NcdfGeneratorException( \"unrecognized time unit \" + unit );\n+\n+\t\t\tarray.setFloat( ima, (float) ret );\n+\t\t}\n+\t\telse {\n+\t\t\tthrow new NcdfGeneratorException( \"Not a timestamp\" );\n+\t\t}\n+\t}\n+}\n+\n+\n+class FloatValueEncoder implements IValueEncoder\n+{\n+\tFloatValueEncoder()\n+\t{\n+\t\tthis.fill = 1234;\n+\t}\n+\n+\tfloat fill;\n+\n+\tpublic DataType targetType()\n+\t{\n+\t\treturn DataType.FLOAT;\n+\t}\n+\n+\tpublic void prepare( Map<String, String> attributes )\n+\t{\n+\t\tfill = Float.valueOf( attributes.get( \"_FillValue\" )).floatValue();\n+\n+\t}\n+\n+\tpublic void encode( Array array, int ima, Object value ) throws NcdfGeneratorException\n+\t{\n+\t\tif( value == null) {\n+\t\t\tarray.setFloat( ima, fill );\n+\t\t}\n+\t\telse if( value instanceof Float ) {\n+\t\t\tarray.setFloat( ima, (Float) value);\n+\t\t}\n+\t\telse if( value instanceof Double ) {\n+\t\t\tarray.setFloat( ima, (float)(double)(Double) value);\n+\t\t}\n+\t\telse {\n+\t\t\tthrow new NcdfGeneratorException( \"Failed to coerce type '\" + value.getClass() + \"' to float\" );\n+\t\t}\n+\t}\n+}\n+\n+\n+class IntValueEncoder implements IValueEncoder\n+{\n+\t// Int is 32bit in Netcdf\n+\n+\tIntValueEncoder()\n+\t{\n+\t\tthis.fill = 1234;\n+\t}\n+\n+\tint fill;\n+\n+\tpublic DataType targetType()\n+\t{\n+\t\treturn DataType.INT;\n+\t}\n+\n+\tpublic void prepare( Map<String, String> attributes )\n+\t{\n+\t\tfill = Integer.valueOf( attributes.get( \"_FillValue\" )).intValue();\n+\t}\n+\n+\tpublic void encode( Array array, int ima, Object value ) throws NcdfGeneratorException\n+\t{\n+\t\tif( value == null) {\n+\t\t\tarray.setInt( ima, fill );\n+\t\t}\n+\t\telse if( value instanceof Integer ) {\n+\t\t\tarray.setInt( ima, (Integer) value);\n+\t\t}\n+\t\telse if( value instanceof Long ) {\n+\t\t\tarray.setInt( ima, (int)(long)(Long) value);\n+\t\t}\n+\t\telse {\n+\t\t\tthrow new NcdfGeneratorException( \"Failed to coerce type '\" + value.getClass() + \"' to float\" );\n+\t\t}\n+\t}\n+}\n+\n+\n+class ByteValueEncoder implements IValueEncoder\n+{\n+\tByteValueEncoder()\n+\t{\n+\t\tthis.fill = 0x0;\n+\t}\n+\tbyte fill;\n+\n+\t// assumption that the Object A is a float array\n+\tpublic DataType targetType()\n+\t{\n+\t\treturn DataType.BYTE;\n+\t}\n+\n+\tpublic void prepare( Map<String, String> attributes )\n+\t{\n+\t\t// eg. no unsigned byte in java, so use integer and downcast\n+\t\tfill = (byte) Integer.decode( attributes.get( \"_FillValue\" ) ).intValue();\n+\t}\n+\n+\tpublic void encode( Array array, int ima, Object value ) throws NcdfGeneratorException\n+\t{\n+\t\tif( value == null) {\n+\t\t\tarray.setByte( ima, fill );\n+\t\t}\n+\t\telse if(value instanceof Byte)\n+\t\t{\n+\t\t\tarray.setByte( ima, (Byte) value);\n+\t\t}\n+\t\telse if(value instanceof String && ((String)value).length() == 1) {\n+\t\t\t// coerce string of length 1 to byte\n+\t\t\tString s = (String) value;\n+\t\t\tByte ch = s.getBytes()[0];\n+\t\t\tarray.setByte(ima, ch);\n+\t\t}\n+\t\telse {\n+\t\t\tthrow new NcdfGeneratorException( \"Failed to convert type to byte\");\n+\t\t}\n+\t}\n+}\n+\n+\n+interface IAddValue\n+{\n+\t// change name to put(), or append? and class to IBufferAddValue\n+\tpublic void addValueToBuffer( Object value );\n+}\n+\n+\n+interface IVariableEncoder extends IAddValue\n+{\n+\n+\tpublic void define( NetcdfFileWriteable writer ) ;\n+\tpublic void finish( NetcdfFileWriteable writer) throws Exception ;\n+\n+\tpublic void addValueToBuffer( Object value );\n+\n+\tpublic String getName(); // change class name to IVariableEncoder and this to just getName()\n+\n+}\n+\n+\n+interface IDimension extends IAddValue\n+{\n+\tpublic void define( NetcdfFileWriteable writer) ;\n+\n+\tpublic Dimension getDimension( ) ; // horrible to expose this...\n+\t\t\t\t\t\t\t\t\t\t// can't the caller create the dimension?\n+\t//\n+\tpublic int getLength();\n+\n+\tpublic void addValueToBuffer( Object value );\n+\n+\tpublic String getName();\n+\n+}\n+\n+\n+class DimensionImpl implements IDimension\n+{\n+\n+\tpublic DimensionImpl( String name )\n+\t{\n+\t\tthis.name = name; // required to encode dimension\n+\t\tthis.size = 0;\n+\t}\n+\n+\tfinal String name;\n+\tint size;\n+\tDimension dimension;\n+\n+\n+\tpublic Dimension getDimension( )  // bad naming\n+\t{\n+\t\t// throw if not defined...\n+\t\treturn dimension;\n+\t}\n+\n+\tpublic int getLength()\n+\t{\n+\t\treturn size;\n+\t}\n+\n+\tpublic void define( NetcdfFileWriteable writer)\n+\t{\n+\t\tdimension = writer.addDimension( name, size );\n+\t}\n+\n+\tpublic void addValueToBuffer( Object value )\n+\t{\n+\t\t++size;\n+\t}\n+\n+\tpublic String getName()\n+\t{\n+\t\treturn name ;\n+\n+\t}\n+\n+}\n+\n+\n+class VariableEncoder implements IVariableEncoder\n+{\n+\tpublic VariableEncoder(\n+\t\tString variableName,\n+\t\tArrayList< IDimension> dimensions,\n+\t\tIValueEncoder encodeValue,\n+\t\tMap<String, String> attributes\n+\t) {\n+\t\tthis.variableName = variableName;\n+\t\tthis.encodeValue = encodeValue;\n+\t\tthis.attributes = attributes;\n+\t\tthis.dimensions = dimensions;\n+\t\tthis.buffer = new ArrayList<Object>( );\n+\t}\n+\n+\tfinal String variableName;\n+\tfinal IValueEncoder\t\t\tencodeValue;\n+\tfinal Map<String, String>\tattributes;\n+\tfinal ArrayList<IDimension>\tdimensions; // change name childDimensions\n+\tfinal ArrayList<Object>\t\tbuffer;\n+\n+\n+\t/*\twe can also record the table, or index of table here if we want\n+\t\t\tto incorporate into the strategy.\n+\t\teg. we can compre with xml to decide what to do.\n+\t*/\n+\tpublic void addValueToBuffer( Object value )\n+\t{\n+\t\t// perhaps delegate to strategy...\n+\t\tbuffer.add( value );\n+\t}\n+\n+\tpublic void define( NetcdfFileWriteable writer )\n+\t{\n+\t\t// write dims and attributes\n+\n+\t\t// make sure children are defined already\n+\t\tList<Dimension> d = new ArrayList<Dimension>();\n+\t\tfor( IDimension dimension: dimensions)\n+\t\t{\n+\t\t\td.add( dimension.getDimension() );\n+\t\t}\n+\n+\t\twriter.addVariable(variableName, encodeValue.targetType(), d );\n+\n+\t\tfor( Map.Entry< String, String> entry : attributes.entrySet()) {\n+\t\t\twriter.addVariableAttribute( variableName, entry.getKey(), entry.getValue()/*.toString()*/ );\n+\t\t}\n+\t}\n+\n+\n+\tpublic void writeValues( ArrayList<IDimension> dims, int dimIndex, int acc, Array A  )\n+\t\tthrows NcdfGeneratorException\n+\t{\n+\t\tif( dimIndex < dims.size() )\n+\t\t{\n+\t\t\tDimension dim = dims.get( dimIndex ).getDimension();\n+\t\t\tfor( int i = 0; i < dim.getLength(); i++ )\n+\t\t\t{\n+\t\t\t\twriteValues( dims, dimIndex + 1, acc + i, A );\n+\t\t\t}\n+\t\t}\n+\t\telse\n+\t\t{\n+\t\t\t// System.out.println( \"dimIndex \" + \"  acc \" + acc  + \"  buffer \" + buffer.get( acc ) );\n+\n+\t\t\tencodeValue.encode( A, acc, buffer.get( acc ) );\n+\t\t}\n+\n+\t}\n+\n+\tprivate static int[] toIntArray( List<Integer> list)\n+\t{\n+\t\t// List.toArray() only supports java Boxed Integers...\n+\t\tint[] ret = new int[list.size()];\n+\t\tfor(int i = 0;i < ret.length;i++)\n+\t\t\tret[i] = list.get(i);\n+\t\treturn ret;\n+\t}\n+\n+\tpublic void finish( NetcdfFileWriteable writer) throws Exception\n+\t{\n+\t\t// System.out.println( \"finish \" + variableName );\n+\n+\t\tArrayList< Integer> shape = new ArrayList< Integer>() ;\n+\t\tfor( IDimension dimension : dimensions ) {\n+\t\t\tshape.add( dimension.getLength() );\n+\t\t}\n+\n+\t\tArray A = Array.factory( encodeValue.targetType(), toIntArray(shape ) );\n+\n+\t\tencodeValue.prepare( attributes );\n+\n+\t\twriteValues( dimensions,  0, 0 , A );\n+\n+\t\t// int [] origin = new int[1];\n+\t\t// writer.write(variableName, origin, A);\n+\t\twriter.write(variableName, A);\n+\t}\n+\n+\n+\tpublic String getName()\n+\t{\n+\t\treturn variableName;\n+\t}\n+}\n+\n+\n+interface ICreateWritable\n+{\n+\tpublic NetcdfFileWriteable create( )  throws IOException ;\n+}\n+\n+\n+class CreateWritable implements  ICreateWritable\n+{\n+\t// an abstraction to support creating the ncf file\n+\t// NetcdfFileWriteable is not an abstraction over a stream!. instead it insists on being a file...\n+\n+\tCreateWritable( String path )\n+\t{\n+\t\tthis.path = path;\n+\t}\n+\n+\tfinal String path;\n+\n+\tpublic NetcdfFileWriteable create() throws IOException\n+\t{\n+\t\treturn NetcdfFileWriteable.createNew( path, false);\n+\t}\n+\n+\t// TODO method to request as a byte stream and return?\n+\t// public getByteStream () { }\n+}\n+\n+\n+class NodeWrapper implements Iterable<Node>\n+{\n+\t// xml helper class\n+\n+    private Node node;\n+    private List<Node> nodes;\n+    private NodeList nodeList;\n+\n+    public NodeWrapper(Node node) {\n+        this.node = node;\n+    }\n+\n+    public Iterator<Node> iterator() {\n+        if (nodes == null) {\n+            buildNodes();\n+        }\n+\n+        return nodes.iterator();\n+    }\n+\n+    private void buildNodes() {\n+        nodes = new ArrayList<Node>(getListLength());\n+        for (int i = 0; i < getListLength(); i++) {\n+            nodes.add(nodeList.item(i));\n+        }\n+    }\n+\n+    private int getListLength() {\n+        return getNodeList().getLength();\n+    }\n+\n+    private NodeList getNodeList() {\n+        if (nodeList == null) {\n+            setNodeList();\n+        }\n+        return nodeList;\n+    }\n+\n+    private void setNodeList() {\n+        if (node.hasChildNodes()) {\n+            nodeList = node.getChildNodes();\n+        }\n+        else {\n+            nodeList = new NullNodeList();\n+        }\n+    }\n+\n+    private class NullNodeList implements NodeList {\n+\n+        public Node item(int index) {\n+            return null;\n+        }\n+\n+        public int getLength() {\n+            return 0;\n+        }\n+    }\n+}\n+\n+\n+class NcdfDefinition\n+{\n+\tNcdfDefinition(\n+\t\tString schema,\n+\t\tString virtualDataTable,\n+\t\tString virtualInstanceTable,\n+\t\tMap< String, IDimension> dimensions,\n+\t\tMap< String, IVariableEncoder> encoders\n+\t) {\n+\t\tthis.schema = schema;\n+\t\tthis.virtualDataTable = virtualDataTable;\n+\t\tthis.virtualInstanceTable = virtualInstanceTable;\n+\t\tthis.dimensions = dimensions;\n+\t\tthis.encoders = encoders;\n+\t}\n+\n+\tfinal String schema;\n+\tfinal String virtualDataTable;\n+\tfinal String virtualInstanceTable;\n+\tfinal Map< String, IDimension> dimensions;\n+\tfinal Map< String, IVariableEncoder> encoders;\n+}\n+\n+\n+class NcdfDefinitionXMLParser\n+{\n+\tprivate boolean isNodeName( Node node, String name )\n+\t{\n+\t\treturn node.getNodeType() == Node.ELEMENT_NODE\n+\t\t\t&& node.getNodeName().equals( name );\n+\t}\n+\n+\tprivate String nodeVal( Node node  )\n+\t{\n+\t\tNode child = node.getFirstChild();\n+\t\tif( child != null && child.getNodeType() == Node.TEXT_NODE )\n+\t\t\treturn child.getNodeValue();\n+\n+\t\treturn \"\";\n+\t}\n+\n+\n+\tprivate Map< String, String> parseKeyVals( Node node )\n+\t{\n+\t\t// have another version that does this for attributes\n+\t\tMap< String, String> m = new HashMap< String, String>();\n+\t\tfor( Node child : new NodeWrapper(node) )\n+\t\t\tif( child.getNodeType() == Node.ELEMENT_NODE )\n+\t\t\t\tm.put( child.getNodeName(), nodeVal( child ) );\n+\n+\t\t// add any xml attributes\n+\t\tNamedNodeMap attrs = node.getAttributes();\n+\t\tfor( int i = 0; i < attrs.getLength(); ++i )\n+\t\t{\n+\t\t\tNode child = attrs.item( i) ;\n+\t\t\tm.put( child.getNodeName(), nodeVal( child ) );\n+\t\t}\n+\n+\t\treturn m;\n+\t}\n+\n+\tprivate IDimension parseDimension( Node node)\n+\t{\n+\t\tif( isNodeName( node, \"dimension\")) {\n+\t\t\tMap< String, String> m = parseKeyVals( node );\n+\t\t\treturn new DimensionImpl( m.get( \"name\" ) );\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\t// having a simple parse key-vals function, means we can do it with attributes as alternative syntax.\n+\n+\tprivate Map< String, IDimension> parseDimensions( Node node )\n+\t{\n+\t\tif( isNodeName( node, \"dimensions\"))\n+\t\t{\n+\t\t\tMap< String, IDimension> dimensions = new HashMap< String, IDimension> () ;\n+\t\t\tfor( Node child : new NodeWrapper(node) ) {\n+\t\t\t\tIDimension dimension = parseDimension( child );\n+\t\t\t\tif( dimension != null)\n+\t\t\t\t\tdimensions.put( dimension.getName(), dimension );\n+\t\t\t}\n+\t\t\treturn dimensions;\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\n+\tprivate IValueEncoder parseEncoder( Node node)\n+\t\tthrows NcdfGeneratorException\n+\t{\n+\t\tif( isNodeName( node, \"encoder\"))\n+\t\t{\n+\t\t\tString val = nodeVal( node );\n+\t\t\tif( val.equals( \"integer\")) {\n+\n+\t\t\t\treturn new IntValueEncoder();\n+\t\t\t\t// throw new RuntimeException( \"INT\" );\n+\t\t\t}\n+\t\t\telse if( val.equals( \"float\")) {\n+\t\t\t\treturn new FloatValueEncoder();\n+\t\t\t}\n+\t\t\telse if( val.equals( \"byte\")) {\n+\t\t\t\treturn new ByteValueEncoder();\n+\t\t\t}\n+\t\t\telse if( val.equals( \"time\")) {\n+\t\t\t\treturn new TimestampValueEncoder();\n+\t\t\t}\n+\t\t\telse\n+\t\t\t{\n+\t\t\t\tthrow new NcdfGeneratorException( \"Unrecognized value type encoder\" );\n+\t\t\t}\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\n+\tprivate SimpleImmutableEntry<String, String> parseAttribute( Node node )\n+\t{\n+\n+\t\tif( isNodeName( node, \"attribute\"))\n+\t\t{\n+\t\t\tMap< String, String> m = parseKeyVals( node );\n+\t\t\tString key = m.get(\"name\");\n+\t\t\tString val = m.get(\"value\");\n+\t\t\treturn new SimpleImmutableEntry< String, String>( key, val );\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\n+\tprivate Map<String, String> parseAttributes( Node node )\n+\t{\n+\t\tif( isNodeName( node, \"attributes\"))\n+\t\t{\n+\t\t\tMap<String, String> m = new HashMap<String, String> ();\n+\t\t\tfor( Node child : new NodeWrapper(node) ) {\n+\t\t\t\tSimpleImmutableEntry< String, String> pair = parseAttribute( child);\n+\t\t\t\tif( pair != null)\n+\t\t\t\t\tm.put( pair.getKey(), pair.getValue());\n+\t\t\t}\n+\t\t\treturn m;\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\n+\tprivate IDimension parseDimensionRef ( Node node, Map< String, IDimension> dimensionsContext )\n+\t{\n+\t\tif( isNodeName( node, \"dimension\")) {\n+\t\t\tMap< String, String> m = parseKeyVals( node );\n+\t\t\t// System.out.println( \"found dimension ref \" + m.get( \"name\" ) );\n+\t\t\treturn dimensionsContext.get( m.get( \"name\" ));\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\n+\tprivate Map< String, IDimension> parseDimensionsRef( Node node, Map< String, IDimension> dimensionsContext )\n+\t{\n+\t\tif( isNodeName( node, \"dimensions\"))\n+\t\t{\n+\t\t\tMap< String, IDimension> dimensions = new HashMap< String, IDimension> () ;\n+\t\t\tfor( Node child : new NodeWrapper(node) ) {\n+\t\t\t\tIDimension dimension = parseDimensionRef( child, dimensionsContext);\n+\t\t\t\tif( dimension != null)\n+\t\t\t\t\tdimensions.put( dimension.getName(), dimension );\n+\t\t\t}\n+\t\t\treturn dimensions;\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\t// think we may want a more general context ...\n+\n+\tprivate IVariableEncoder parseVariableEncoder( Node node, Map< String, IDimension> dimensionsContext  )\n+\t\tthrows NcdfGeneratorException\n+\t{\n+\t\tString name = null;\n+\t\tMap< String, IDimension> dimensions = null;\n+\t\tIValueEncoder encodeValue = null;\n+\t\tMap< String, String> attributes = null;\n+\n+\t\tif( isNodeName( node, \"variable\"))\n+\t\t{\n+\t\t\tfor( Node child : new NodeWrapper(node) )\n+\t\t\t{\n+\t\t\t\t// this is very neat. may want to do this explicitly rather than using the map...\n+\t\t\t\tif( isNodeName( child, \"name\" ))\n+\t\t\t\t\tname = nodeVal( child);\n+\t\t\t\tif( dimensions == null)\n+\t\t\t\t\tdimensions = parseDimensionsRef( child, dimensionsContext );\n+\t\t\t\tif( encodeValue == null)\n+\t\t\t\t\tencodeValue = parseEncoder( child ) ;\n+\t\t\t\tif( attributes == null)\n+\t\t\t\t\tattributes = parseAttributes( child );\n+\t\t\t}\n+\n+\t\t\tif( dimensions == null )\n+\t\t\t{\n+\t\t\t\tdimensions = new HashMap< String, IDimension> ();\n+\t\t\t}\n+\n+\t\t\tif( name != null\n+\t\t\t\t&& encodeValue != null\n+\t\t\t\t&& attributes != null )\n+\t\t\t{\n+\t\t\t\tSystem.out.println( \"whoot creating encoder \" + name  );\n+\n+\t\t\t\treturn new VariableEncoder ( name , new ArrayList<IDimension>(dimensions.values()), encodeValue , attributes ) ;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\tthrow new NcdfGeneratorException( \"missing something\" );\n+\t\t\t\t// return null;\n+\t\t\t}\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\n+\tprivate Map< String, IVariableEncoder> parseVariableEncoders( Node node, Map< String, IDimension> dimensionsContext  )\n+\t\tthrows NcdfGeneratorException\n+\t{\n+\t\tif( isNodeName( node, \"variables\"))\n+\t\t{\n+\t\t\tMap< String, IVariableEncoder> m = new HashMap < String, IVariableEncoder>();\n+\t\t\tfor( Node child : new NodeWrapper(node)) {\n+\t\t\t\tIVariableEncoder e = parseVariableEncoder( child, dimensionsContext  );\n+\t\t\t\tif( e != null )\n+\t\t\t\t\tm.put( e.getName(), e );\n+\t\t\t}\n+\t\t\treturn m;\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\tprivate Map< String, String> parseSource( Node node )\n+\t{\n+\t\tif( isNodeName( node, \"source\")) /// data or dataSource\n+\t\t{\n+\t\t\tMap< String, String> source = parseKeyVals( node);\n+\n+\t\t\t/*\n+\t\t\tSystem.out.println( \"*** GOT CONFIG \" +  source.size()   );\n+\t\t\tfor( Map.Entry< String, String> entry : source.entrySet()) {\n+\t\t\t\tSystem.out.println( \"*** \" + entry.getKey() + \" \" + entry.getValue() );\n+\n+\t\t\t} */\n+\t\t\treturn source;\n+\t\t}\n+\t\treturn null;\n+\n+\t}\n+\n+\tNcdfDefinition parseDefinition( Node node )\n+\t\tthrows NcdfGeneratorException\n+\t{\n+\t\t// think we need a context?\n+\t\tif( isNodeName( node, \"definition\"))\n+\t\t{\n+\t\t\tMap< String, String> source = null;\n+\t\t\tMap< String, IDimension> dimensions = null;\n+\t\t\tMap< String, IVariableEncoder> encoders = null;\n+\n+\t\t\t// pick out dimensions\n+\t\t\tfor( Node child : new NodeWrapper(node)) {\n+\n+\t\t\t\tif( source == null )\n+\t\t\t\t\tsource = parseSource( child );\n+\n+\t\t\t\tif( dimensions == null )\n+\t\t\t\t\tdimensions = parseDimensions( child );\n+\n+\t\t\t\tif( encoders == null )\n+\t\t\t\t\tencoders = parseVariableEncoders( child, dimensions );\n+\t\t\t}\n+\n+\t\t\tString schema = source.get( \"schema\" );\n+\t\t\tString virtualDataTable = source.get( \"virtualDataTable\" );\n+\t\t\tString virtualInstanceTable =source.get( \"virtualInstanceTable\" );\n+\n+\t\t\treturn new NcdfDefinition( schema, virtualDataTable, virtualInstanceTable, dimensions, encoders );\n+\t\t}\n+\t\treturn null;\n+\t}\n+}\n+\n+\n+class NcdfEncoder\n+{\n+\tfinal IExprParser exprParser;\n+\tfinal IDialectTranslate translate ;\n+\tfinal Connection conn;\n+\tfinal ICreateWritable createWritable; // generate a writable\n+\tfinal NcdfDefinition definition ;\n+\tfinal String filterExpr;\n+\n+\tfinal int fetchSize;\n+\tIExpression selection_expr;\n+\tResultSet featureInstancesRS;\n+\n+\tpublic NcdfEncoder(\n+\t\tIExprParser exprParser,\n+\t\tIDialectTranslate translate,\n+\t\tConnection conn,\n+\t\tICreateWritable createWritable,\n+\t\tNcdfDefinition definition,\n+\t\tString filterExpr\n+\t) {\n+\t\tthis.exprParser = exprParser;\n+\t\tthis.translate = translate; // sqlEncode.. dialect... specialization\n+\t\tthis.conn = conn;\n+\t\tthis.createWritable = createWritable;\n+\t\tthis.definition = definition;\n+\t\tthis.filterExpr = filterExpr;\n+\n+\t\tfetchSize = 1000;\n+\t\tfeatureInstancesRS = null;\n+\t\tselection_expr = null;\n+\t}\n+\n+\tpublic void prepare() throws Exception\n+\t{\n+\t\tselection_expr = exprParser.parseExpression( filterExpr, 0);\n+\t\t// bad, should return expr or throw\n+\t\tif(selection_expr == null) {\n+\t\t\tthrow new NcdfGeneratorException( \"failed to parse expression\" );\n+\t\t}\n+\n+\t\tSystem.out.println( \"setting search_path to \" + definition.schema );\n+\n+\t\tPreparedStatement s = conn.prepareStatement(\"set search_path='\" + definition.schema + \"'\");\n+\t\t// PreparedStatement s = conn.prepareStatement(\"set search_path='\" + schema + \"',public\");\n+\t\t// PreparedStatement s = conn.prepareStatement(\"set search_path=\" + schema + \",public\");\n+\t\ts.execute();\n+\t\ts.close();\n+\n+\t\tString selection = translate.process( selection_expr);\n+\n+\t\tString query = \"SELECT distinct data.instance_id  FROM (\" + definition.virtualDataTable + \") as data where \" + selection + \";\" ;\n+\t\tSystem.out.println( \"first query \" + query  );\n+\n+\t\tPreparedStatement stmt = conn.prepareStatement( query );\n+\t\tstmt.setFetchSize(fetchSize);\n+\n+\t\t// try ...\n+\t\t// change name featureInstancesRSToProcess ?\n+\t\tfeatureInstancesRS = stmt.executeQuery();\n+\t\tSystem.out.println( \"done determining feature instances \" );\n+\t\t// should determine our target types here\n+\t}\n+\n+\tpublic void populateValues(\n+\t\tMap< String, IDimension> dimensions,\n+\t\tMap< String, IVariableEncoder> encoders,\n+\t\tString query\n+\t\t)  throws Exception\n+\t{\n+\t\tSystem.out.println( \"query \" + query  );\n+\n+\t\t// sql stuff\n+\t\tPreparedStatement stmt = conn.prepareStatement( query );\n+\t\tstmt.setFetchSize(fetchSize);\n+\t\tResultSet rs = stmt.executeQuery();\n+\n+\t\t// now we loop the main attributes\n+\t\tResultSetMetaData m = rs.getMetaData();\n+\t\tint numColumns = m.getColumnCount();\n+\n+\t\t// pre-map the encoders by index according to the column name\n+\t\tArrayList< IAddValue> [] processing = (ArrayList< IAddValue> []) new ArrayList [numColumns + 1];\n+\n+\t\tfor ( int i = 1 ; i <= numColumns ; i++ ) {\n+\t\t\t// System.out.println( \"column name \"+ m.getColumnName(i) );\n+\t\t\tprocessing[i] = new ArrayList< IAddValue> ();\n+\n+\t\t\tIDimension dimension = dimensions.get( m.getColumnName(i));\n+\t\t\tif( dimension != null)\n+\t\t\t\tprocessing[i].add( dimension );\n+\n+\t\t\tIAddValue encoder = encoders.get(m.getColumnName(i));\n+\t\t\tif( encoder != null)\n+\t\t\t\tprocessing[i].add( encoder );\n+\t\t}\n+\n+\t\t// process result set rows\n+\t\twhile ( rs.next() ) {\n+\t\t\tfor ( int i = 1 ; i <= numColumns ; i++ ) {\n+\t\t\t\tfor( IAddValue p : processing[ i] ) {\n+\t\t\t\t\tp.addValueToBuffer( rs.getObject( i));\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\n+\tpublic NetcdfFileWriteable get() throws Exception\n+\t{\n+\t\t// TODO should just return a readable IStream, client shouldn't care that it's netcdf type.\n+\n+\t\ttry {\n+\t\t\tif( featureInstancesRS.next())\n+\t\t\t{\n+\t\t\t\t// munge\n+\t\t\t\tlong instance_id = -1234;\n+\t\t\t\tObject o = featureInstancesRS.getObject(1);\n+\t\t\t\tClass clazz = o.getClass();\n+\t\t\t\tif( clazz.equals( Integer.class )) {\n+\t\t\t\t\tinstance_id = (long)(Integer)o;\n+\t\t\t\t}\n+\t\t\t\telse if( clazz.equals( Long.class )) {\n+\t\t\t\t\tinstance_id = (long)(Long)o;\n+\t\t\t\t} else {\n+\t\t\t\t\tthrow new NcdfGeneratorException( \"Can't convert intance_id type to integer\" );\n+\t\t\t\t}\n+\n+\t\t\t\tSystem.out.println( \"whoot get(), instance_id is \" + instance_id );\n+\n+\t\t\t\tString selection = translate.process( selection_expr); // we ought to be caching the specific query ???\n+\n+\t\t\t\tpopulateValues( definition.dimensions, definition.encoders,\n+\t\t\t\t\t\"SELECT * FROM (\" + definition.virtualInstanceTable + \") as instance where instance.id = \" + Long.toString( instance_id) );","path":"src/main/java/au/org/emii/ncdfgenerator/NcdfEncoderBuilder.java","position":1381,"original_position":1381,"commit_id":"8f70922052eee666e9cb89100b75e743a5b80c52","original_commit_id":"8f70922052eee666e9cb89100b75e743a5b80c52","user":{"login":"anguss00","id":5964166,"avatar_url":"https://avatars.githubusercontent.com/u/5964166?v=3","gravatar_id":"","url":"https://api.github.com/users/anguss00","html_url":"https://github.com/anguss00","followers_url":"https://api.github.com/users/anguss00/followers","following_url":"https://api.github.com/users/anguss00/following{/other_user}","gists_url":"https://api.github.com/users/anguss00/gists{/gist_id}","starred_url":"https://api.github.com/users/anguss00/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anguss00/subscriptions","organizations_url":"https://api.github.com/users/anguss00/orgs","repos_url":"https://api.github.com/users/anguss00/repos","events_url":"https://api.github.com/users/anguss00/events{/privacy}","received_events_url":"https://api.github.com/users/anguss00/received_events","type":"User","site_admin":false},"body":"Perhaps use String.format with these kind of queries? Including the definition.virtualInstanceTable etc as variables?","created_at":"2015-04-16T03:14:12Z","updated_at":"2015-04-16T03:14:12Z","html_url":"https://github.com/aodn/netcdf_filters/pull/2#discussion_r28481337","pull_request_url":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2","_links":{"self":{"href":"https://api.github.com/repos/aodn/netcdf_filters/pulls/comments/28481337"},"html":{"href":"https://github.com/aodn/netcdf_filters/pull/2#discussion_r28481337"},"pull_request":{"href":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2","id":33310475,"html_url":"https://github.com/aodn/netcdf_filters/pull/2","diff_url":"https://github.com/aodn/netcdf_filters/pull/2.diff","patch_url":"https://github.com/aodn/netcdf_filters/pull/2.patch","issue_url":"https://api.github.com/repos/aodn/netcdf_filters/issues/2","number":2,"state":"open","locked":false,"title":"Mybranch","user":{"login":"julian1","id":4312379,"avatar_url":"https://avatars.githubusercontent.com/u/4312379?v=3","gravatar_id":"","url":"https://api.github.com/users/julian1","html_url":"https://github.com/julian1","followers_url":"https://api.github.com/users/julian1/followers","following_url":"https://api.github.com/users/julian1/following{/other_user}","gists_url":"https://api.github.com/users/julian1/gists{/gist_id}","starred_url":"https://api.github.com/users/julian1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/julian1/subscriptions","organizations_url":"https://api.github.com/users/julian1/orgs","repos_url":"https://api.github.com/users/julian1/repos","events_url":"https://api.github.com/users/julian1/events{/privacy}","received_events_url":"https://api.github.com/users/julian1/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-04-14T23:36:24Z","updated_at":"2015-04-16T03:14:12Z","closed_at":null,"merged_at":null,"merge_commit_sha":"62fa4625225729d97372551fbd96b48843c4d76f","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2/commits","review_comments_url":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2/comments","review_comment_url":"https://api.github.com/repos/aodn/netcdf_filters/pulls/comments{/number}","comments_url":"https://api.github.com/repos/aodn/netcdf_filters/issues/2/comments","statuses_url":"https://api.github.com/repos/aodn/netcdf_filters/statuses/8f70922052eee666e9cb89100b75e743a5b80c52","head":{"label":"aodn:mybranch","ref":"mybranch","sha":"8f70922052eee666e9cb89100b75e743a5b80c52","user":{"login":"aodn","id":2802166,"avatar_url":"https://avatars.githubusercontent.com/u/2802166?v=3","gravatar_id":"","url":"https://api.github.com/users/aodn","html_url":"https://github.com/aodn","followers_url":"https://api.github.com/users/aodn/followers","following_url":"https://api.github.com/users/aodn/following{/other_user}","gists_url":"https://api.github.com/users/aodn/gists{/gist_id}","starred_url":"https://api.github.com/users/aodn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aodn/subscriptions","organizations_url":"https://api.github.com/users/aodn/orgs","repos_url":"https://api.github.com/users/aodn/repos","events_url":"https://api.github.com/users/aodn/events{/privacy}","received_events_url":"https://api.github.com/users/aodn/received_events","type":"Organization","site_admin":false},"repo":{"id":33917023,"name":"netcdf_filters","full_name":"aodn/netcdf_filters","owner":{"login":"aodn","id":2802166,"avatar_url":"https://avatars.githubusercontent.com/u/2802166?v=3","gravatar_id":"","url":"https://api.github.com/users/aodn","html_url":"https://github.com/aodn","followers_url":"https://api.github.com/users/aodn/followers","following_url":"https://api.github.com/users/aodn/following{/other_user}","gists_url":"https://api.github.com/users/aodn/gists{/gist_id}","starred_url":"https://api.github.com/users/aodn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aodn/subscriptions","organizations_url":"https://api.github.com/users/aodn/orgs","repos_url":"https://api.github.com/users/aodn/repos","events_url":"https://api.github.com/users/aodn/events{/privacy}","received_events_url":"https://api.github.com/users/aodn/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/aodn/netcdf_filters","description":"","fork":false,"url":"https://api.github.com/repos/aodn/netcdf_filters","forks_url":"https://api.github.com/repos/aodn/netcdf_filters/forks","keys_url":"https://api.github.com/repos/aodn/netcdf_filters/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aodn/netcdf_filters/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aodn/netcdf_filters/teams","hooks_url":"https://api.github.com/repos/aodn/netcdf_filters/hooks","issue_events_url":"https://api.github.com/repos/aodn/netcdf_filters/issues/events{/number}","events_url":"https://api.github.com/repos/aodn/netcdf_filters/events","assignees_url":"https://api.github.com/repos/aodn/netcdf_filters/assignees{/user}","branches_url":"https://api.github.com/repos/aodn/netcdf_filters/branches{/branch}","tags_url":"https://api.github.com/repos/aodn/netcdf_filters/tags","blobs_url":"https://api.github.com/repos/aodn/netcdf_filters/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aodn/netcdf_filters/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aodn/netcdf_filters/git/refs{/sha}","trees_url":"https://api.github.com/repos/aodn/netcdf_filters/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aodn/netcdf_filters/statuses/{sha}","languages_url":"https://api.github.com/repos/aodn/netcdf_filters/languages","stargazers_url":"https://api.github.com/repos/aodn/netcdf_filters/stargazers","contributors_url":"https://api.github.com/repos/aodn/netcdf_filters/contributors","subscribers_url":"https://api.github.com/repos/aodn/netcdf_filters/subscribers","subscription_url":"https://api.github.com/repos/aodn/netcdf_filters/subscription","commits_url":"https://api.github.com/repos/aodn/netcdf_filters/commits{/sha}","git_commits_url":"https://api.github.com/repos/aodn/netcdf_filters/git/commits{/sha}","comments_url":"https://api.github.com/repos/aodn/netcdf_filters/comments{/number}","issue_comment_url":"https://api.github.com/repos/aodn/netcdf_filters/issues/comments{/number}","contents_url":"https://api.github.com/repos/aodn/netcdf_filters/contents/{+path}","compare_url":"https://api.github.com/repos/aodn/netcdf_filters/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aodn/netcdf_filters/merges","archive_url":"https://api.github.com/repos/aodn/netcdf_filters/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aodn/netcdf_filters/downloads","issues_url":"https://api.github.com/repos/aodn/netcdf_filters/issues{/number}","pulls_url":"https://api.github.com/repos/aodn/netcdf_filters/pulls{/number}","milestones_url":"https://api.github.com/repos/aodn/netcdf_filters/milestones{/number}","notifications_url":"https://api.github.com/repos/aodn/netcdf_filters/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aodn/netcdf_filters/labels{/name}","releases_url":"https://api.github.com/repos/aodn/netcdf_filters/releases{/id}","created_at":"2015-04-14T07:23:52Z","updated_at":"2015-04-14T07:23:52Z","pushed_at":"2015-04-16T02:11:04Z","git_url":"git://github.com/aodn/netcdf_filters.git","ssh_url":"git@github.com:aodn/netcdf_filters.git","clone_url":"https://github.com/aodn/netcdf_filters.git","svn_url":"https://github.com/aodn/netcdf_filters","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"aodn:master","ref":"master","sha":"31cd9b0d0eb5d61b804fff5bbc3c81eda08b0468","user":{"login":"aodn","id":2802166,"avatar_url":"https://avatars.githubusercontent.com/u/2802166?v=3","gravatar_id":"","url":"https://api.github.com/users/aodn","html_url":"https://github.com/aodn","followers_url":"https://api.github.com/users/aodn/followers","following_url":"https://api.github.com/users/aodn/following{/other_user}","gists_url":"https://api.github.com/users/aodn/gists{/gist_id}","starred_url":"https://api.github.com/users/aodn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aodn/subscriptions","organizations_url":"https://api.github.com/users/aodn/orgs","repos_url":"https://api.github.com/users/aodn/repos","events_url":"https://api.github.com/users/aodn/events{/privacy}","received_events_url":"https://api.github.com/users/aodn/received_events","type":"Organization","site_admin":false},"repo":{"id":33917023,"name":"netcdf_filters","full_name":"aodn/netcdf_filters","owner":{"login":"aodn","id":2802166,"avatar_url":"https://avatars.githubusercontent.com/u/2802166?v=3","gravatar_id":"","url":"https://api.github.com/users/aodn","html_url":"https://github.com/aodn","followers_url":"https://api.github.com/users/aodn/followers","following_url":"https://api.github.com/users/aodn/following{/other_user}","gists_url":"https://api.github.com/users/aodn/gists{/gist_id}","starred_url":"https://api.github.com/users/aodn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aodn/subscriptions","organizations_url":"https://api.github.com/users/aodn/orgs","repos_url":"https://api.github.com/users/aodn/repos","events_url":"https://api.github.com/users/aodn/events{/privacy}","received_events_url":"https://api.github.com/users/aodn/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/aodn/netcdf_filters","description":"","fork":false,"url":"https://api.github.com/repos/aodn/netcdf_filters","forks_url":"https://api.github.com/repos/aodn/netcdf_filters/forks","keys_url":"https://api.github.com/repos/aodn/netcdf_filters/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aodn/netcdf_filters/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aodn/netcdf_filters/teams","hooks_url":"https://api.github.com/repos/aodn/netcdf_filters/hooks","issue_events_url":"https://api.github.com/repos/aodn/netcdf_filters/issues/events{/number}","events_url":"https://api.github.com/repos/aodn/netcdf_filters/events","assignees_url":"https://api.github.com/repos/aodn/netcdf_filters/assignees{/user}","branches_url":"https://api.github.com/repos/aodn/netcdf_filters/branches{/branch}","tags_url":"https://api.github.com/repos/aodn/netcdf_filters/tags","blobs_url":"https://api.github.com/repos/aodn/netcdf_filters/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aodn/netcdf_filters/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aodn/netcdf_filters/git/refs{/sha}","trees_url":"https://api.github.com/repos/aodn/netcdf_filters/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aodn/netcdf_filters/statuses/{sha}","languages_url":"https://api.github.com/repos/aodn/netcdf_filters/languages","stargazers_url":"https://api.github.com/repos/aodn/netcdf_filters/stargazers","contributors_url":"https://api.github.com/repos/aodn/netcdf_filters/contributors","subscribers_url":"https://api.github.com/repos/aodn/netcdf_filters/subscribers","subscription_url":"https://api.github.com/repos/aodn/netcdf_filters/subscription","commits_url":"https://api.github.com/repos/aodn/netcdf_filters/commits{/sha}","git_commits_url":"https://api.github.com/repos/aodn/netcdf_filters/git/commits{/sha}","comments_url":"https://api.github.com/repos/aodn/netcdf_filters/comments{/number}","issue_comment_url":"https://api.github.com/repos/aodn/netcdf_filters/issues/comments{/number}","contents_url":"https://api.github.com/repos/aodn/netcdf_filters/contents/{+path}","compare_url":"https://api.github.com/repos/aodn/netcdf_filters/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aodn/netcdf_filters/merges","archive_url":"https://api.github.com/repos/aodn/netcdf_filters/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aodn/netcdf_filters/downloads","issues_url":"https://api.github.com/repos/aodn/netcdf_filters/issues{/number}","pulls_url":"https://api.github.com/repos/aodn/netcdf_filters/pulls{/number}","milestones_url":"https://api.github.com/repos/aodn/netcdf_filters/milestones{/number}","notifications_url":"https://api.github.com/repos/aodn/netcdf_filters/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aodn/netcdf_filters/labels{/name}","releases_url":"https://api.github.com/repos/aodn/netcdf_filters/releases{/id}","created_at":"2015-04-14T07:23:52Z","updated_at":"2015-04-14T07:23:52Z","pushed_at":"2015-04-16T02:11:04Z","git_url":"git://github.com/aodn/netcdf_filters.git","ssh_url":"git@github.com:aodn/netcdf_filters.git","clone_url":"https://github.com/aodn/netcdf_filters.git","svn_url":"https://github.com/aodn/netcdf_filters","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2"},"html":{"href":"https://github.com/aodn/netcdf_filters/pull/2"},"issue":{"href":"https://api.github.com/repos/aodn/netcdf_filters/issues/2"},"comments":{"href":"https://api.github.com/repos/aodn/netcdf_filters/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/aodn/netcdf_filters/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/aodn/netcdf_filters/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/aodn/netcdf_filters/statuses/8f70922052eee666e9cb89100b75e743a5b80c52"}}}}