// context for simple expression // context means: // - the local variables // - the local variable names // - the lookup-input variables package btools.expressions; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.Locale; import btools.util.BitCoderContext; import btools.util.Crc32; import btools.util.IByteArrayUnifier; import btools.util.LruMap; public abstract class BExpressionContext implements IByteArrayUnifier { private static final String CONTEXT_TAG = "---context:"; private static final String MODEL_TAG = "---model:"; private String context; private boolean _inOurContext = false; private BufferedReader _br = null; private boolean _readerDone = false; public String _modelClass; private Map lookupNumbers = new HashMap(); private ArrayList lookupValues = new ArrayList(); private ArrayList lookupNames = new ArrayList(); private ArrayList lookupHistograms = new ArrayList(); private boolean[] lookupIdxUsed; private boolean lookupDataFrozen = false; private int[] lookupData = new int[0]; private byte[] abBuf = new byte[256]; private BitCoderContext ctxEndode = new BitCoderContext( abBuf ); private BitCoderContext ctxDecode = new BitCoderContext( new byte[0] ); private Map variableNumbers = new HashMap(); private float[] variableData; // hash-cache for function results private CacheNode probeCacheNode = new CacheNode(); private LruMap cache; private VarWrapper probeVarSet = new VarWrapper(); private LruMap resultVarCache; private List expressionList; private int minWriteIdx; // build-in variable indexes for fast access private int[] buildInVariableIdx; private int nBuildInVars; private float[] currentVars; private int currentVarOffset; private BExpressionContext foreignContext; protected void setInverseVars() { currentVarOffset = nBuildInVars; } abstract String[] getBuildInVariableNames(); public final float getBuildInVariable( int idx ) { return currentVars[idx+currentVarOffset]; } private int linenr; public BExpressionMetaData meta; private boolean lookupDataValid = false; protected BExpressionContext( String context, BExpressionMetaData meta ) { this( context, 4096, meta ); } /** * Create an Expression-Context for the given node * * @param context global, way or node - context of that instance * @param hashSize size of hashmap for result caching */ protected BExpressionContext( String context, int hashSize, BExpressionMetaData meta ) { this.context = context; this.meta = meta; if ( meta != null ) meta.registerListener(context, this ); if ( Boolean.getBoolean( "disableExpressionCache" ) ) hashSize = 1; // create the expression cache if ( hashSize > 0 ) { cache = new LruMap( 4*hashSize, hashSize ); resultVarCache = new LruMap( 4096, 4096 ); } } /** * encode internal lookup data to a byte array */ public byte[] encode() { if ( !lookupDataValid ) throw new IllegalArgumentException( "internal error: encoding undefined data?" ); return encode( lookupData ); } public byte[] encode( int[] ld ) { BitCoderContext ctx = ctxEndode; ctx.reset(); int skippedTags = 0; int nonNullTags= 0; // (skip first bit ("reversedirection") ) // all others are generic for( int inum = 1; inum < lookupValues.size(); inum++ ) // loop over lookup names { int d = ld[inum]; if ( d == 0 ) { skippedTags++; continue; } ctx.encodeVarBits( skippedTags+1 ); nonNullTags++; skippedTags = 0; // 0 excluded already, 1 (=unknown) we rotate up to 8 // to have the good code space for the popular values int dd = d < 2 ? 7 : ( d < 9 ? d - 2 : d - 1); ctx.encodeVarBits( dd ); } ctx.encodeVarBits( 0 ); if ( nonNullTags == 0) return null; int len = ctx.closeAndGetEncodedLength(); byte[] ab = new byte[len]; System.arraycopy( abBuf, 0, ab, 0, len ); // crosscheck: decode and compare int[] ld2 = new int[lookupValues.size()]; decode( ld2, false, ab ); for( int inum = 1; inum < lookupValues.size(); inum++ ) // loop over lookup names (except reverse dir) { if ( ld2[inum] != ld[inum] ) throw new RuntimeException( "assertion failed encoding inum=" + inum + " val=" + ld[inum] + " " + getKeyValueDescription(false, ab) ); } return ab; } /** * decode byte array to internal lookup data */ public void decode( byte[] ab ) { decode( lookupData, false, ab ); lookupDataValid = true; } /** * decode a byte-array into a lookup data array */ private void decode( int[] ld, boolean inverseDirection, byte[] ab ) { BitCoderContext ctx = ctxDecode; ctx.reset( ab ); // start with first bit hardwired ("reversedirection") ld[0] = inverseDirection ? 2 : 0; // all others are generic int inum = 1; for(;;) { int delta = ctx.decodeVarBits(); if ( delta == 0) break; if ( inum + delta > ld.length ) break; // higher minor version is o.k. while ( delta-- > 1 ) ld[inum++] = 0; // see encoder for value rotation int dd = ctx.decodeVarBits(); int d = dd == 7 ? 1 : ( dd < 7 ? dd + 2 : dd + 1); if ( d >= lookupValues.get(inum).length && d < 1000) d = 1; // map out-of-range to unknown ld[inum++] = d; } while( inum < ld.length ) ld[inum++] = 0; } public String getKeyValueDescription( boolean inverseDirection, byte[] ab ) { StringBuilder sb = new StringBuilder( 200 ); decode( lookupData, inverseDirection, ab ); for( int inum = 0; inum < lookupValues.size(); inum++ ) // loop over lookup names { BExpressionLookupValue[] va = lookupValues.get(inum); int val = lookupData[inum]; String value = (val>=1000) ? Float.toString((val-1000)/100f) : va[val].toString(); if ( value != null && value.length() > 0 ) { if ( sb.length() > 0 ) sb.append( ' ' ); sb.append(lookupNames.get( inum ) + "=" + value ); } } return sb.toString(); } public List getKeyValueList( boolean inverseDirection, byte[] ab ) { ArrayList res = new ArrayList(); decode( lookupData, inverseDirection, ab ); for( int inum = 0; inum < lookupValues.size(); inum++ ) // loop over lookup names { BExpressionLookupValue[] va = lookupValues.get(inum); int val = lookupData[inum]; // no negative values String value = (val>=1000) ? Float.toString((val-1000)/100f) : va[val].toString(); if ( value != null && value.length() > 0 ) { res.add( lookupNames.get( inum ) ); res.add( value ); } } return res; } public int getLookupKey(String name) { int res = -1; try { res = lookupNumbers.get(name).intValue(); } catch (Exception e ) {} return res; } public float getLookupValue(int key) { float res = 0f; int val = lookupData[key]; if (val == 0) return Float.NaN; res = (val-1000)/100f; return res; } public float getLookupValue(boolean inverseDirection, byte[] ab, int key) { float res = 0f; decode( lookupData, inverseDirection, ab ); int val = lookupData[key]; if (val == 0) return Float.NaN; res = (val-1000)/100f; return res; } private int parsedLines = 0; private boolean fixTagsWritten = false; public void parseMetaLine( String line ) { parsedLines++; StringTokenizer tk = new StringTokenizer( line, " " ); String name = tk.nextToken(); String value = tk.nextToken(); int idx = name.indexOf( ';' ); if ( idx >= 0 ) name = name.substring( 0, idx ); if ( !fixTagsWritten ) { fixTagsWritten = true; if ( "way".equals( context ) ) addLookupValue( "reversedirection", "yes", null ); else if ( "node".equals( context ) ) addLookupValue( "nodeaccessgranted", "yes", null ); } if ( "reversedirection".equals( name ) ) return; // this is hardcoded if ( "nodeaccessgranted".equals( name ) ) return; // this is hardcoded BExpressionLookupValue newValue = addLookupValue( name, value, null ); // add aliases while( newValue != null && tk.hasMoreTokens() ) newValue.addAlias( tk.nextToken() ); } public void finishMetaParsing() { if ( parsedLines == 0 && !"global".equals(context) ) { throw new IllegalArgumentException( "lookup table does not contain data for context " + context + " (old version?)" ); } // post-process metadata: lookupDataFrozen = true; lookupIdxUsed = new boolean[lookupValues.size()]; } public final void evaluate( int[] lookupData2 ) { lookupData = lookupData2; evaluate(); } private void evaluate() { int n = expressionList.size(); for( int expidx = 0; expidx < n; expidx++ ) { expressionList.get( expidx ).evaluate( this ); } } private long requests; private long requests2; private long cachemisses; public String cacheStats() { return "requests=" + requests + " requests2=" + requests2 + " cachemisses=" + cachemisses; } private CacheNode lastCacheNode = new CacheNode(); // @Override public final byte[] unify( byte[] ab, int offset, int len ) { probeCacheNode.ab = null; // crc based cache lookup only probeCacheNode.hash = Crc32.crc( ab, offset, len ); CacheNode cn = (CacheNode)cache.get( probeCacheNode ); if ( cn != null ) { byte[] cab = cn.ab; if ( cab.length == len ) { for( int i=0; i counts = new TreeMap(); // first count for( String name: lookupNumbers.keySet() ) { int cnt = 0; int inum = lookupNumbers.get(name).intValue(); int[] histo = lookupHistograms.get(inum); // if ( histo.length == 500 ) continue; for( int i=2; i 0 ) { String key = counts.lastEntry().getKey(); String name = counts.get(key); counts.remove( key ); int inum = lookupNumbers.get(name).intValue(); BExpressionLookupValue[] values = lookupValues.get(inum); int[] histo = lookupHistograms.get(inum); if ( values.length == 1000 ) continue; String[] svalues = new String[values.length]; for( int i=0; i=0; i-- ) { System.out.println( name + ";" + svalues[i] ); } } } /** * @return a new lookupData array, or null if no metadata defined */ public int[] createNewLookupData() { if ( lookupDataFrozen ) { return new int[lookupValues.size()]; } return null; } /** * generate random values for regression testing */ public int[] generateRandomValues( Random rnd ) { int[] data = createNewLookupData(); data[0] = 2*rnd.nextInt( 2 ); // reverse-direction = 0 or 2 for( int inum = 1; inum < data.length; inum++ ) { int nvalues = lookupValues.get( inum ).length; data[inum] = 0; if ( inum > 1 && rnd.nextInt( 10 ) > 0 ) continue; // tags other than highway only 10% data[inum] = rnd.nextInt( nvalues ); } lookupDataValid = true; return data; } public void assertAllVariablesEqual( BExpressionContext other ) { int nv = variableData.length; int nv2 = other.variableData.length; if ( nv != nv2 ) throw new RuntimeException( "mismatch in variable-count: " + nv + "<->" + nv2 ); for( int i=0; i" + other.variableData[i] + "\ntags = " + getKeyValueDescription( false, encode() ) ); } } } public String variableName( int idx ) { for( Map.Entry e : variableNumbers.entrySet() ) { if ( e.getValue().intValue() == idx ) { return e.getKey(); } } throw new RuntimeException( "no variable for index" + idx ); } /** * add a new lookup-value for the given name to the given lookupData array. * If no array is given (null value passed), the value is added to * the context-binded array. In that case, unknown names and values are * created dynamically. * * @return a newly created value element, if any, to optionally add aliases */ public BExpressionLookupValue addLookupValue( String name, String value, int[] lookupData2 ) { BExpressionLookupValue newValue = null; Integer num = lookupNumbers.get( name ); if ( num == null ) { if ( lookupData2 != null ) { // do not create unknown name for external data array return newValue; } // unknown name, create num = Integer.valueOf( lookupValues.size() ); lookupNumbers.put( name, num ); lookupNames.add( name ); lookupValues.add( new BExpressionLookupValue[]{ new BExpressionLookupValue( "" ) , new BExpressionLookupValue( "unknown" ) } ); lookupHistograms.add( new int[2] ); int[] ndata = new int[lookupData.length+1]; System.arraycopy( lookupData, 0, ndata, 0, lookupData.length ); lookupData = ndata; } // look for that value int inum = num.intValue(); BExpressionLookupValue[] values = lookupValues.get( inum ); int[] histo = lookupHistograms.get( inum ); int i=0; boolean bFoundAsterix = false; for( ; i", ""); value = value.replace("_", ""); if (value.indexOf("-") == 0) value = value.substring(1); if (value.indexOf("~") == 0) value = value.substring(1); if (value.contains("-")) { // replace eg. 1.4-1.6 m String tmp = value.substring(value.indexOf("-")+1).replaceAll("[0-9.,-]", ""); value = value.substring(0, value.indexOf("-")) + tmp; } // do some value conversion if (value.toLowerCase().contains("ft")) { float foot = 0f; int inch = 0; String[] sa = value.toLowerCase().trim().split("ft"); if (sa.length >= 1) foot = Float.parseFloat(sa[0].trim()); if (sa.length == 2) { value = sa[1]; if (value.indexOf("in") > 0) value = value.substring(0,value.indexOf("in")); inch = Integer.parseInt(value.trim()); foot += inch/12f; } value = String.format(Locale.US, "%3.1f", foot*0.3048f); } if (value.toLowerCase().contains("'")) { float foot = 0f; int inch = 0; String[] sa = value.toLowerCase().trim().split("'"); if (sa.length >= 1) foot = Float.parseFloat(sa[0].trim()); if (sa.length == 2) { value = sa[1]; if (value.indexOf("''") > 0) value = value.substring(0,value.indexOf("''")); if (value.indexOf("\"") > 0) value = value.substring(0,value.indexOf("\"")); inch = Integer.parseInt(value.trim()); foot += inch/12f; } value = String.format(Locale.US, "%3.1f", foot*0.3048f); } else if (value.contains("in") || value.contains("\"")) { float inch = 0f; if (value.indexOf("in") > 0) value = value.substring(0,value.indexOf("in")); if (value.indexOf("\"") > 0) value = value.substring(0,value.indexOf("\"")); inch = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f",inch*0.0254f); } else if (value.toLowerCase().contains("feet") || value.toLowerCase().contains("foot")) { float feet = 0f; String s = value.substring(0, value.toLowerCase().indexOf("f") ); feet = Float.parseFloat(s.trim()); value = String.format(Locale.US, "%3.1f", feet*0.3048f); } else if (value.toLowerCase().contains("fathom") || value.toLowerCase().contains("fm")) { float fathom = 0f; String s = value.substring(0, value.toLowerCase().indexOf("f") ); fathom = Float.parseFloat(s.trim()); value = String.format(Locale.US, "%3.1f", fathom*1.8288f); } else if (value.contains("cm")) { String[] sa = value.trim().split("cm"); if (sa.length == 1) value = sa[0].trim(); float cm = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", cm*100f); } else if (value.toLowerCase().contains("meter")) { String s = value.substring(0, value.toLowerCase().indexOf("m") ); value = s.trim(); } else if (value.toLowerCase().contains("mph")) { value = value.replace("_", ""); String[] sa = value.trim().toLowerCase().split("mph"); if (sa.length >= 1) value = sa[0].trim(); float mph = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", mph*1.609344f); } else if (value.toLowerCase().contains("knot")) { String[] sa = value.trim().toLowerCase().split("knot"); if (sa.length >= 1) value = sa[0].trim(); float nm = Float.parseFloat(value.trim()); value = String.format(Locale.US, "%3.1f", nm*1.852f); } else if (value.contains("kmh") || value.contains("km/h") || value.contains("kph")) { String[] sa = value.trim().split("k"); if (sa.length == 1) value = sa[0].trim(); } else if (value.contains("m")) { String s = value.substring(0, value.toLowerCase().indexOf("m") ); value = s.trim(); } else if (value.contains("(")) { String s = value.substring(0, value.toLowerCase().indexOf("(") ); value = s.trim(); } // found negative maxdraft values // no negative values // values are float with 2 decimals lookupData2[inum] = 1000 + (int)(Math.abs(Float.parseFloat(value))*100f); } catch ( Exception e) { // ignore errors System.err.println( "error for " + name + " " + org + " trans " + value + " " + e.getMessage()); lookupData2[inum] = 0; } } return newValue; } if ( i == 499 ) { // System.out.println( "value limit reached for: " + name ); } if ( i == 500 ) { return newValue; } // unknown value, create BExpressionLookupValue[] nvalues = new BExpressionLookupValue[values.length+1]; int[] nhisto = new int[values.length+1]; System.arraycopy( values, 0, nvalues, 0, values.length ); System.arraycopy( histo, 0, nhisto, 0, histo.length ); values = nvalues; histo = nhisto; newValue = new BExpressionLookupValue( value ); values[i] = newValue; lookupHistograms.set(inum, histo); lookupValues.set(inum, values); } histo[i]++; // finally remember the actual data if ( lookupData2 != null ) lookupData2[inum] = i; else lookupData[inum] = i; return newValue; } /** * add a value-index to to internal array * value-index means 0=unknown, 1=other, 2=value-x, ... */ public void addLookupValue( String name, int valueIndex ) { Integer num = lookupNumbers.get( name ); if ( num == null ) { return; } // look for that value int inum = num.intValue(); int nvalues = lookupValues.get( inum ).length; if ( valueIndex < 0 || valueIndex >= nvalues ) throw new IllegalArgumentException( "value index out of range for name " + name + ": " + valueIndex ); lookupData[inum] = valueIndex; } /** * special hack for yes/proposed relations: * add a lookup value if not yet a smaller, > 1 value was added * add a 2=yes if the provided value is out of range * value-index means here 0=unknown, 1=other, 2=yes, 3=proposed */ public void addSmallestLookupValue( String name, int valueIndex ) { Integer num = lookupNumbers.get( name ); if ( num == null ) { return; } // look for that value int inum = num.intValue(); int nvalues = lookupValues.get( inum ).length; int oldValueIndex = lookupData[inum]; if ( oldValueIndex > 1 && oldValueIndex < valueIndex ) { return; } if ( valueIndex >= nvalues ) { valueIndex = nvalues-1; } if ( valueIndex < 0 ) throw new IllegalArgumentException( "value index out of range for name " + name + ": " + valueIndex ); lookupData[inum] = valueIndex; } public boolean getBooleanLookupValue( String name ) { Integer num = lookupNumbers.get( name ); return num != null && lookupData[num.intValue()] == 2; } public int getOutputVariableIndex( String name, boolean mustExist ) { int idx = getVariableIdx( name, false ); if ( idx < 0 ) { if ( mustExist ) { throw new IllegalArgumentException( "unknown variable: " + name ); } } else if ( idx < minWriteIdx ) { throw new IllegalArgumentException( "bad access to global variable: " + name ); } for( int i=0; i _parseFile( File file ) throws Exception { _br = new BufferedReader( new FileReader( file ) ); _readerDone = false; List result = new ArrayList(); for(;;) { BExpression exp = BExpression.parse( this, 0 ); if ( exp == null ) break; result.add( exp ); } _br.close(); _br = null; return result; } public void setVariableValue(String name, float value, boolean create ) { Integer num = variableNumbers.get( name ); if ( num != null ) { variableData[num.intValue()] = value; } } public float getVariableValue( String name, float defaultValue ) { Integer num = variableNumbers.get( name ); return num == null ? defaultValue : getVariableValue( num.intValue() ); } float getVariableValue( int variableIdx ) { return variableData[variableIdx]; } int getVariableIdx( String name, boolean create ) { Integer num = variableNumbers.get( name ); if ( num == null ) { if ( create ) { num = new Integer( variableNumbers.size() ); variableNumbers.put( name, num ); } else { return -1; } } return num.intValue(); } int getMinWriteIdx() { return minWriteIdx; } float getLookupMatch( int nameIdx, int[] valueIdxArray ) { for( int i=0; i 0 ) return sb.toString(); else continue; } if ( c == '#' && sb.length() == 0 ) inComment = true; else sb.append( c ); } } float assign( int variableIdx, float value ) { variableData[variableIdx] = value; return value; } }