/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package flex2.compiler.config; import java.util.Map; import java.util.HashMap; import java.util.List; import java.util.Iterator; import java.util.Set; import java.util.HashSet; import java.util.LinkedList; import java.util.ArrayList; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.InvocationTargetException; import java.io.UnsupportedEncodingException; import flex2.compiler.io.VirtualFile; /** * The basic idea here is to let you keep all your configuration knowledge in your configuration object, * and to automate as much as possible. Reflection is used to convert public fields and setters on your * configuration object into settable vars. There are a few key concepts: * <p> * - You should be able to configure absolutely any object.<br> * - Child configuration variables in your config become a dotted hierarchy of varnames<br> * - All sources of configuration data are buffered and merged (as string var/vals) before * committing to the final configuration. This class acts as the buffer.<br> * - Hyphenated variables (i.e. "some-var") are automatically configured by calling your matching setter (i.e. setSomeVar)<br> * - Implementing an getSomeVarInfo() method on your class lets you set up more complicated config objects<br> * - You can make variables depend on other variables having been set first. This lets you set a * root directory in one var and then use its value in another.<br> * - Per-variable validation can be performed in setters. Overall validation should take place * as a post-process step.<br> * - You can keep ConfigurationBuffers around and merge multiple buffers together before committing. * Most recent definitions always win.<br> * <p> * The contract with your configuration class: * <p> * - You must provide a method with the signature "void setYourVar(ConfigurationValue val)" to set your config var. * Your setter method should accept either a single arg of type List or String[], or else an arglist of * simple types. For example "void myvar(int a, boolean b, String c")".<br> * - You can implement a function with the signature "int yourvar_argcount()" to require a different number * of arguments. This limit will be enforced by configurators (command line, file, etc.)<br> * - If you provide a setter and explicit parameters (i.e. not List or String[]) the number of arguments * will be automatically determined.<br> * - Each argument to your configuration variable is assumed to have a (potentially non-unique) name. The default is * the simple type of the argument (boolean, int, string). If the var takes an undetermined number of args via * List or String[], the argname defaults to string.<br> * - You can implement a function with the signature "String yourvar_argnames(int)" to provide names * for each of the parameters. The integer passed in is the argument number. Return the same name * (i.e. "item") for infinite lists.<br> * - You can implement a function with the signature "String[] yourvar_deps()" to provide a list * of other prerequisites for this var. You will be guaranteed that the deps are committed before * your var, or else a configurationexception will be thrown if a prerequsite was unset. (Note that * infinite cycles are not checked, so be careful.)<br> * * @author Roger Gonzalez */ public final class ConfigurationBuffer { public ConfigurationBuffer( Class configClass ) { this( configClass, new HashMap<String, String>() ); } public ConfigurationBuffer( Class configClass, Map<String, String> aliases ) { this(configClass, aliases, null); } /** * Create a configuration buffer with an optional filter. The filter can be used * to remove unwanted options from a super class. * * @param filter if null there is no filter, otherwise the set of configuration options * is filtered. */ public ConfigurationBuffer( Class configClass, Map<String, String> aliases, ConfigurationFilter filter ) { this.configClass = configClass; this.varMap = new HashMap<String, List>(); this.committed = new HashSet<String>(); loadCache( configClass, null, filter ); assert ( varCache.size() > 0 ) : "coding error: nothing was configurable in the provided object!"; for (Iterator it = aliases.entrySet().iterator(); it.hasNext(); ) { Map.Entry e = (Map.Entry) it.next(); addAlias( (String) e.getKey(), (String) e.getValue() ); } } public ConfigurationBuffer( ConfigurationBuffer copyFrom, boolean copyCommitted ) { this.configClass = copyFrom.configClass; this.varMap = new HashMap<String, List>( copyFrom.varMap ); this.committed = copyCommitted? new HashSet<String>( copyFrom.committed ) : new HashSet<String>(); this.varCache = copyFrom.varCache; // doesn't change after creation this.childCache = copyFrom.childCache; // doesn't change after creation; this.varList = copyFrom.varList; // doesn't change after creation this.tokens = new HashMap<String, String>( copyFrom.tokens ); } public void setVar( String var, String val, String source, int line ) throws ConfigurationException { List<String> list = new LinkedList<String>(); list.add( val ); setVar( var, list, source, line, null, false ); } public void setVar( String var, List<String> vals, String source, int line ) throws ConfigurationException { setVar( var, vals, source, line, null, false ); } public void setVar( String avar, List<String> vals, String source, int line, String contextPath, boolean append ) throws ConfigurationException { String var = unalias( avar ); if (!isValidVar( var )) throw new ConfigurationException.UnknownVariable( var, source, line ); int argCount = getVarArgCount( var ); // -1 means unspecified length, its up to the receiving setter to validate. if (argCount != -1) { if (vals.size() != argCount) { throw new ConfigurationException.IncorrectArgumentCount( argCount, // expected vals.size(), //passed var, source, line ); } } ConfigurationValue val = new ConfigurationValue( this, var, vals, //processValues( var, vals, source, line ), source, line, contextPath ); storeValue( var, val, append ); committed.remove( var ); } public void clearVar( String avar, String source, int line ) throws ConfigurationException { String var = unalias( avar ); if (!isValidVar( var )) throw new ConfigurationException.UnknownVariable( var, source, line ); varMap.remove( var ); committed.remove( var ); } public void clearSourceVars( String source ) { List<String> remove = new LinkedList<String>(); for (Iterator it = varMap.entrySet().iterator(); it.hasNext();) { Map.Entry e = (Map.Entry) it.next(); String var = (String) e.getKey(); List vals = (List) e.getValue(); List<ConfigurationValue> newvals = new LinkedList<ConfigurationValue>(); for (Iterator vi = vals.iterator(); vi.hasNext();) { ConfigurationValue val = (ConfigurationValue) vi.next(); if (!val.getSource().equals( source )) { newvals.add( val ); } } if (newvals.size() > 0) varMap.put( var, newvals ); else remove.add( var ); } for (Iterator<String> it = remove.iterator(); it.hasNext();) { varMap.remove( it.next() ); } } public List<String> processValues( String var, List<String> args, String source, int line ) throws ConfigurationException { List<String> newArgs = new LinkedList<String>(); for (Iterator<String> it = args.iterator(); it.hasNext();) { String arg = it.next(); int depth = 100; while (depth-- > 0) { int o = arg.indexOf( "${" ); if (o == -1) break; int c = arg.indexOf( "}", o ); if (c == -1) { throw new ConfigurationException.Token(ConfigurationException.Token.MISSING_DELIMITER, null, var, source, line ); } String token = arg.substring( o + 2, c ); String value = getToken( token ); if (value == null) { /* if (false && isValidVar( token )) { if (varMap.containsKey( token )) { List vals = varMap.get( token ); assert ( vals.size() > 0 ); if (vals.size() > 1) { throw new ConfigurationException.Token( ConfigurationException.Token.MULTIPLE_VALUES, token, var, source, line ); } ConfigurationValue first = (ConfigurationValue) vals.get( 0 ); if (first.getArgs().size() != 1) { throw new ConfigurationException.Token( ConfigurationException.Token.MULTIPLE_VALUES, token, var, source, line ); } value = first.getArgs().get( 0 ); } } */ throw new ConfigurationException.Token( ConfigurationException.Token.UNKNOWN_TOKEN, token, var, source, line ); } arg = arg.substring( 0, o ) + value + arg.substring( c + 1 ); } if (depth == 0) { throw new ConfigurationException.Token( ConfigurationException.Token.RECURSION_LIMIT, null, var, source, line ); } newArgs.add( arg ); } return newArgs; } public void setToken( String token, String value ) { tokens.put( token, value ); } public String getToken( String token ) { if (tokens.containsKey( token )) return tokens.get( token ); else { try { return System.getProperty( token ); } catch (SecurityException se) { return null; } } } @SuppressWarnings("unchecked") private void storeValue( String avar, ConfigurationValue val, boolean append ) throws ConfigurationException { String var = unalias( avar ); ConfigurationInfo info = getInfo( var ); List<ConfigurationValue> vals; if (varMap.containsKey( var )) { vals = varMap.get( var ); assert ( vals.size() > 0 ); ConfigurationValue first = vals.get( 0 ); if (!append && !first.getSource().equals( val.getSource() )) vals.clear(); else if (!info.allowMultiple()) throw new ConfigurationException.IllegalMultipleSet( var, val.getSource(), val.getLine() ); } else { vals = new LinkedList<ConfigurationValue>(); varMap.put( var, vals ); } vals.add( val ); } public List getVar( String avar ) { String var = unalias( avar ); return varMap.get( var ); } public Iterator<String> getVarIterator() { return varCache.keySet().iterator(); } /* private Iterator<String> getSetVarIterator() { return varMap.keySet().iterator(); } */ public void merge( ConfigurationBuffer other ) { assert ( configClass == other.configClass ); varMap.putAll( other.varMap ); committed.addAll( other.committed ); } public void mergeChild( String prefix, ConfigurationBuffer child ) { assert isChildConfig( prefix ) : "coding error: " + prefix + " is not a child configuration object."; for (Iterator<Map.Entry<String, List>> it = child.varMap.entrySet().iterator(); it.hasNext();) { Map.Entry<String, List> e = it.next(); varMap.put( prefix + "." + e.getKey(), e.getValue() ); } for (Iterator<String> it = child.committed.iterator(); it.hasNext();) { String var = it.next(); committed.add( prefix + "." + var ); } } private final Map<String, List> varMap; // list of vars that have been set private final Set<String> committed; // set of vars committed to backing config private final Class configClass; // configuration class private Map<String, ConfigurationInfo> varCache // info cache = new HashMap<String, ConfigurationInfo>(); private List<String> requiredList = new LinkedList<String>(); // required vars private List<String> varList = new LinkedList<String>(); // list of vars in order they should be set private Map<String, Class> childCache = new HashMap<String, Class>(); // child configuration objects private Map<String, String> aliases = new HashMap<String, String>(); // variable name aliases private Map<String, String> tokens = new HashMap<String, String>(); // tokens for replacement private List<Object[]> positions = new ArrayList<Object[]>(); private static final String SET_PREFIX = "cfg"; private static final String GET_PREFIX = "get"; private static final String CONFIGURATION_SUFFIX = "Configuration"; private static final String INFO_SUFFIX = "Info"; //----------------------------------------------- // /** * convert StudlyCaps or camelCase to hyphenated * @param camel someVar or SomeVar * @return hyphen some-var */ protected static String c2h( String camel ) { StringBuilder b = new StringBuilder(camel.length() + 5); for (int i = 0; i < camel.length(); ++i) { char c = camel.charAt(i); if (Character.isUpperCase( c )) { if (i != 0) b.append( '-' ); b.append( Character.toLowerCase( c ) ); } else { b.append( camel.charAt(i) ); } } return b.toString(); } /** * convert hyphenated to StudlyCaps or camelCase * @param hyphenated some-var * @return result */ protected static String h2c( String hyphenated, boolean studly ) { StringBuilder b = new StringBuilder( hyphenated.length() ); boolean capNext = studly; for (int i = 0; i < hyphenated.length(); ++i) { char c = hyphenated.charAt(i); if (c == '-') capNext = true; else { b.append( capNext? Character.toUpperCase( c ) : c ); capNext = false; } } return b.toString(); } private static String varname( String membername, String basename ) { return ((basename == null)? membername : (basename + "." + membername)); } @SuppressWarnings("unchecked") private static ConfigurationInfo createInfo( Method setterMethod ) { ConfigurationInfo info = null; String infoMethodName = GET_PREFIX + setterMethod.getName().substring( SET_PREFIX.length() ) + INFO_SUFFIX; String getterMethodName = GET_PREFIX + setterMethod.getName().substring( SET_PREFIX.length() ); Class cfgClass = setterMethod.getDeclaringClass(); Method infoMethod, getterMethod = null; try { infoMethod = cfgClass.getMethod( infoMethodName, (Class[])null); if (Modifier.isStatic( infoMethod.getModifiers() ) ) { info = (ConfigurationInfo) infoMethod.invoke(null, (Object[]) null); getterMethod = cfgClass.getMethod(getterMethodName, (Class[]) null); } else { assert false : ( "coding error: " + cfgClass.getName() + "." + infoMethodName + " needs to be static!" ); } } catch (Exception e) { // Ignore } if (info == null) { info = new ConfigurationInfo(); } info.setSetterMethod( setterMethod ); info.setGetterMethod( getterMethod ); return info; } /* private static ConfigurationInfo createChildInfo( Method childGetMethod ) { ConfigurationInfo info = null; int cfgIndex = childGetMethod.getName().lastIndexOf( CONFIGURATION_SUFFIX ); assert cfgIndex != -1; String infoMethodName = childGetMethod.getName().substring( 0, cfgIndex ) + INFO_SUFFIX; Class cfgClass = childGetMethod.getDeclaringClass(); Method infoMethod = null; try { infoMethod = cfgClass.getMethod( infoMethodName, (Class[])null); if (!Modifier.isStatic( infoMethod.getModifiers() ) ) { assert false : ( "coding error: " + cfgClass.getName() + "." + infoMethodName + " needs to be static!" ); infoMethod = null; } info = (ConfigurationInfo) infoMethod.invoke( null, (Object[])null ); info.setSetterMethod( null ); assert info.getAliases() == null : "coding error: child configurations cannot have aliases."; assert info.getArgCount() == 0 : "coding error: child configurations do not have arguments"; assert info.getArgName( 0 ) == null : "coding error: child configuraitons do not have argnames"; } catch (Exception e) { return null; } if (info == null) { info = new ConfigurationInfo(); } return info; } */ /** * load - prefetch all the interesting names into a dictionary so that we can find them * again more easily. At the end of this call, we will have a list of every variable * and their associated method. * * @param filter if null there is no filter, otherwise the set of configuration options * is filtered. */ private boolean loadCache( Class cfg, String basename, ConfigurationFilter filter) { int count = 0; // First, find all vars at this level. Method methods[] = cfg.getMethods(); for (int m = 0; m < methods.length; ++m) { Method method = methods[m]; if (method.getName().startsWith( SET_PREFIX )) { Class[] pt = method.getParameterTypes(); if ((pt.length > 1) && (pt[0] == ConfigurationValue.class)) { // This is an autoconfiguration setter! ConfigurationInfo info = createInfo( method ); String leafname = c2h( method.getName().substring( SET_PREFIX.length() ) ); String name = varname( leafname, basename ); if (filter == null || filter.select(name)) { varCache.put( name, info ); varList.add( name ); if (info.isRequired()) { requiredList.add( name ); } ++count; } } } } // Now find all children. for (int m = 0; m < methods.length; ++m) { Method method = methods[m]; if (method.getName().startsWith( GET_PREFIX ) && method.getName().endsWith( CONFIGURATION_SUFFIX )) { String leafname = c2h( method.getName().substring( GET_PREFIX.length(), method.getName().length() - CONFIGURATION_SUFFIX.length())); String fullname = varname( leafname, basename ); if (loadCache( method.getReturnType(), fullname, filter )) { childCache.put( fullname, method.getReturnType() ); ++count; } } else { continue; } } assert ( count > 0 || filter != null) : "coding error: config class " + cfg.getName() + " did not define any setters or child configs"; return (count > 0); } String classToArgName( Class c ) { // we only support builtin classnames! String className = c.getName(); if (className.startsWith( "java.lang." )) className = className.substring( "java.lang.".length() ); return className.toLowerCase(); } public ConfigurationInfo getInfo( String avar ) { String var = unalias( avar ); return varCache.get( var ); } String getVarArgName( String avar, int argnum ) { String var = unalias( avar ); ConfigurationInfo info = getInfo( var ); assert info == null : ( "must call isValid to check vars!" ); if (info != null) { return info.getArgName( argnum ); } return null; } public boolean isValidVar( String avar ) { String var = unalias( avar ); ConfigurationInfo info = getInfo( var ); return (info != null); } public boolean isChildConfig( String var ) { return childCache.keySet().contains( var ); } public Class getChildConfigClass( String var ) { return childCache.get( var ); } int getVarArgCount( String avar ) { ConfigurationInfo info = getInfo( avar ); assert ( info != null ); return info.getArgCount(); } /** * commit - bake the resolved map to the configuration */ public void commit( Object config ) throws ConfigurationException { assert ( config.getClass() == configClass ) : ( "coding error: configuration " + config.getClass() + " != template " + configClass ); Set<String> done = new HashSet<String>(); for (Iterator<String> vars = varList.iterator(); vars.hasNext(); ) { String var = vars.next(); if (varMap.containsKey( var )) { commitVariable( config, var, done ); } } for (Iterator<String> reqs = requiredList.iterator(); reqs.hasNext();) { String req = reqs.next(); if (!committed.contains( req )) { throw new ConfigurationException.MissingRequirement( req, null, null, -1 ); } } } /** * commitVariable - copy a variable out of a state into the final config. * This should only be called on variables that are known to exist in the state! * * @param var variable name to lookup * @param done set of variable names that have been completed so far (for recursion) */ private void commitVariable( Object config, String var, Set<String> done ) throws ConfigurationException { ConfigurationInfo info = getInfo( var ); setPrerequisites(info.getPrerequisites(), var, done, config, true); setPrerequisites(info.getSoftPrerequisites(), var, done, config, false); if (committed.contains( var )) return; committed.add( var ); done.add( var ); assert ( varMap.containsKey( var ) ); List vals = varMap.get( var ); if (vals.size() > 1) { assert ( info.allowMultiple() ); // assumed to have been previously checked } for (Iterator valit = vals.iterator(); valit.hasNext();) { ConfigurationValue val = (ConfigurationValue) valit.next(); try { Object targetconfig = getParentConfiguration( config, var ); Object[] args = buildArgList( info, val ); info.getSetterMethod().invoke( targetconfig, args ); calculateChecksum(targetconfig, info, var, args); } catch (Exception e) { Throwable t = e; if (e instanceof InvocationTargetException) { t = ((InvocationTargetException)e).getTargetException(); } //if (Trace.error) t.printStackTrace(); if (t instanceof ConfigurationException) { throw (ConfigurationException)t; } else { throw new ConfigurationException.OtherThrowable(t, var, val.getSource(), val.getLine() ); } } } } private void setPrerequisites(String[] prerequisites, String var, Set<String> done, Object config, boolean required) throws ConfigurationException { if (prerequisites != null) { for (int p = 0; p < prerequisites.length; ++p) { String depvar = prerequisites[p]; // Dependencies can only go downward. int dot = var.lastIndexOf( '.' ); if (dot >= 0) { String car = var.substring( 0, dot ); //String cdr = var.substring( dot + 1 ); depvar = car + "." + depvar; } if (!done.contains( depvar )) { if (!isValidVar( depvar )) { assert false : ( "invalid " + var + " dependency " + depvar ); continue; } if (varMap.containsKey( depvar )) { commitVariable( config, depvar, done ); } else if (required && !committed.contains( depvar )) { // FIXME - can we get source/line for this? throw new ConfigurationException.MissingRequirement(depvar, var, null, -1); } } } } } Object getParentConfiguration( Object config, String varname ) { int dot = varname.indexOf( '.' ); // FIXME? should be lastIndexOf? --rg String getConfigName; if (dot < 0) { // varname is in current config. return config; } else { String car = varname.substring( 0, dot ); String cdr = varname.substring( dot + 1 ); getConfigName = GET_PREFIX + h2c( car, true ) + CONFIGURATION_SUFFIX; try { Method getCfgMethod = config.getClass().getMethod( getConfigName, (Class[])null ); Object child = getCfgMethod.invoke( config, (Object[])null ); return getParentConfiguration( child, cdr ); } catch (NoSuchMethodException e) { assert false : ( "impossible: should have already confirmed this!" ); } catch (InvocationTargetException e) { assert false : ( "coding error: bad child config getter" ); } catch (IllegalAccessException e) { assert false : ( "coding error: bad child config getter" ); } return null; } } private String[] constructStringArray( List<String> args ) { String[] sa = new String[args.size()]; int i = 0; for (Iterator<String> it = args.iterator(); it.hasNext();) sa[i++] = it.next(); return sa; } private Object constructValueObject( ConfigurationInfo info, ConfigurationValue cv ) throws ConfigurationException { try { Class[] pt = info.getSetterMethod().getParameterTypes(); assert ( pt.length == 2 ); // assumed to be checked upstream Object o = pt[1].newInstance(); Field[] fields = pt[1].getFields(); assert ( fields.length == cv.getArgs().size() ); // assumed to be checked upstream Iterator argsit = cv.getArgs().iterator(); for (int f = 0; f < fields.length; ++f) { String val = (String) argsit.next(); Object valobj = null; Class fc = fields[f].getType(); assert ( info.getArgType( f ) == fc ); assert ( info.getArgName( f ).equals( ConfigurationBuffer.c2h( fields[f].getName() )) ); if (fc == String.class) { valobj = val; } else if ((fc == Boolean.class) || (fc == boolean.class)) { // TODO - Boolean.valueOf is pretty lax. Maybe we should restrict to true/false? valobj = Boolean.valueOf( val ); } else if ((fc == Integer.class) || (fc == int.class)) { valobj = Integer.decode( val ); } else if ((fc == Long.class) || (fc == long.class)) { valobj = Long.decode( val ); } else { assert false; // should have checked any other condition upstream! } fields[f].set( o, valobj ); } return o; } catch (InstantiationException e) { assert false : ( "coding error: unable to instantiate value object when trying to set var " + cv.getVar() ); throw new ConfigurationException.OtherThrowable( e, cv.getVar(), cv.getSource(), cv.getLine() ); } catch (IllegalAccessException e) { assert false : ( "coding error: " + e + " when trying to set var " + cv.getVar() ); throw new ConfigurationException.OtherThrowable( e, cv.getVar(), cv.getSource(), cv.getLine() ); } } protected static boolean isSupportedSimpleType( Class c ) { return ((c == String.class) || (c == Integer.class) || (c == int.class) || (c == Long.class) || (c == long.class) || (c == Boolean.class) || (c == boolean.class)); } protected static boolean isSupportedListType( Class c ) { return ((c == List.class) || (c == String[].class)); } protected static boolean isSupportedValueType( Class c ) { if (isSupportedSimpleType( c )) return false; Field[] fields = c.getFields(); for (int f = 0; f < fields.length; ++f) { if (!isSupportedSimpleType( fields[f].getType() )) return false; } return true; } private Object[] buildArgList( ConfigurationInfo info, ConfigurationValue val ) throws ConfigurationException { Method setter = info.getSetterMethod(); Class<?>[] pt = setter.getParameterTypes(); List<String> args = processValues( val.getVar(), val.getArgs(), val.getSource(), val.getLine() ); if (info.getArgCount() == -1) { if (pt.length != 2) { assert false : ( "coding error: unlimited length setter " + val.getVar() + " must take a single argument of type List or String[]" ); return null; } else if (List.class.isAssignableFrom( pt[1] )) { return new Object[] { val, args }; } else if (String[].class.isAssignableFrom( pt[1] )) { return new Object[] {val, constructStringArray( args )}; } else { assert false : ( "coding error: unlimited length setter " + val.getVar() + " must take a single argument of type List or String[]" ); return null; } } else { assert ( pt.length > 1 ) : ( "coding error: config setter " + val.getVar() + " must accept at least one argument" ); // ok, we first check to see if the signature of their setter accepts a list. if (pt.length == 2) { // a variety of specialty setters here... if (List.class.isAssignableFrom( pt[1] )) { return new Object[] { val, args }; } else if (String[].class == pt[1]) { return new Object[] { val, constructStringArray( args ) }; } else if (isSupportedValueType( pt[1] )) { return new Object[] { val, constructValueObject( info, val ) }; } } // otherwise, they must have a matching size parm list as the number of args passed in. assert ( pt.length == (args.size() + 1) ) : ( "coding error: config setter " + val.getVar() + " does not have " + args.size() + " parameters!" ); Object[] pa = new Object[pt.length]; pa[0] = val; for (int p = 1; p < pt.length; ++p) { String arg = args.get(p-1); if (pt[p].isAssignableFrom( String.class )) { pa[p] = arg; } else if ((pt[p] == int.class) || (pt[p] == Integer.class)) { try { pa[p] = Integer.decode( arg ); } catch (Exception e) { throw new ConfigurationException.TypeMismatch( ConfigurationException.TypeMismatch.INTEGER, arg, val.getVar(), val.getSource(), val.getLine() ); } } else if ((pt[p] == long.class) || (pt[p] == Long.class)) { try { pa[p] = Long.decode( arg ); } catch (Exception e) { throw new ConfigurationException.TypeMismatch( ConfigurationException.TypeMismatch.LONG, arg, val.getVar(), val.getSource(), val.getLine() ); } } else if ((pt[p] == boolean.class) || (pt[p] == Boolean.class)) { try { arg = arg.trim().toLowerCase(); if ( arg.equals( "true" ) || arg.equals( "false" ) ) { pa[p] = Boolean.valueOf( arg ); } else { throw new ConfigurationException.TypeMismatch( ConfigurationException.TypeMismatch.BOOLEAN, arg, val.getVar(), val.getSource(), val.getLine() ); } } catch (Exception e) { throw new ConfigurationException.TypeMismatch( ConfigurationException.TypeMismatch.BOOLEAN, arg, val.getVar(), val.getSource(), val.getLine() ); } } else { assert false : ( "coding error: " + val.getVar() + " setter argument " + p + " is not a supported type" ); } } return pa; } } public void addAlias( String alias, String var ) { if (!isValidVar( var )) { assert false : ( "coding error: can't bind alias " + alias + " to nonexistent var " + var ); return; } if (aliases.containsKey( alias )) { assert false : ( "coding error: alias " + alias + " already defined as " + aliases.get( alias )); return; } if (varCache.containsKey( alias )) { assert false : ( "coding error: can't define alias " + alias + ", it already exists as a var" ); return; } aliases.put( alias, var ); } public Map<String, String> getAliases() { return aliases; } public String unalias( String var ) { String realvar = aliases.get( var ); return (realvar == null)? var : realvar; } public String peekSimpleConfigurationVar(String avar) throws ConfigurationException { String val = null; List valList = getVar(avar); if (valList != null) { ConfigurationValue cv = (ConfigurationValue) valList.get(0); List<String> args = processValues(avar, cv.getArgs(), cv.getSource(), cv.getLine()); val = args.get(0); } return val; } public List<ConfigurationValue> peekConfigurationVar(String avar) throws ConfigurationException { List srcList = getVar(avar); if (srcList == null) return null; List<ConfigurationValue> dstList = new LinkedList<ConfigurationValue>(); for (Iterator it = srcList.iterator(); it.hasNext();) { ConfigurationValue srcVal = (ConfigurationValue) it.next(); List<String> args = processValues(avar, srcVal.getArgs(), srcVal.getSource(), srcVal.getLine()); ConfigurationValue dstVal = new ConfigurationValue( srcVal.getBuffer(), avar, args, srcVal.getSource(), srcVal.getLine(), srcVal.getContext()); dstList.add( dstVal ); } return dstList; } public void addPosition(String var, int iStart, int iEnd) { positions.add(new Object[] { var, new Integer(iStart), new Integer(iEnd) }); } public List<Object[]> getPositions() { return positions; } // C: checksum calculation for Configuration. checksum() is based on config values. checksum_ts() is // based on config values + timestamps from VirtualFile-based config values. public void setDefaultVar(String var) { defaultVar = var; } private String defaultVar; private StringBuilder compile_checksum = new StringBuilder(); private StringBuilder compile_checksum_ts = new StringBuilder(); private StringBuilder link_checksum = new StringBuilder(); private StringBuilder link_checksum_ts = new StringBuilder(); private void calculateChecksum(Object targetConfig, ConfigurationInfo info, String var, Object[] args) throws Exception { // C: don't use default var to calculate checksum... if (var != null && var.equals(defaultVar)) { return; } // C: we always update link_checksum and link_checksum_ts. if (info.doChecksum()) compile_checksum.append(var); link_checksum.append(var); for (int i = 1; i < args.length; i++) { if (info.getGetterMethod() != null) { Class retType = info.getGetterMethod().getReturnType(); if (VirtualFile.class.isAssignableFrom(retType)) { VirtualFile file = (VirtualFile) info.getGetterMethod().invoke(targetConfig, (Object[])null); if (file != null) { if (info.doChecksum()) compile_checksum.append(file.getName()); link_checksum.append(file.getName()); } continue; } else if (retType.isArray() && VirtualFile.class.isAssignableFrom(retType.getComponentType())) { VirtualFile[] files = (VirtualFile[]) info.getGetterMethod().invoke(targetConfig, (Object[])null); for (int j = 0; files != null && j < files.length; j++) { if (files[j] != null) { if (info.doChecksum()) compile_checksum.append(files[j].getName()); link_checksum.append(files[j].getName()); } } continue; } } if (args[i] instanceof Object[]) { Object[] a = (Object[]) args[i]; for (int j = 0; j < a.length; j++) { if (info.doChecksum()) compile_checksum.append(a[j]); link_checksum.append(a[j]); } } else if (args[i] instanceof List) { List l = (List) args[i]; for (int j = 0; j < l.size(); j++) { if (info.doChecksum()) compile_checksum.append(l.get(j)); link_checksum.append(l.get(j)); } } else { if (info.doChecksum()) compile_checksum.append(args[i]); link_checksum.append(args[i]); } } if (info.getGetterMethod() == null) { // C: need to make sure that all the VirtualFile-based config values should have getters. return; } Class retType = info.getGetterMethod().getReturnType(); if (VirtualFile.class.isAssignableFrom(retType)) { VirtualFile file = (VirtualFile) info.getGetterMethod().invoke(targetConfig, (Object[])null); if (file != null && !file.isDirectory()) { if (info.doChecksum()) compile_checksum_ts.append(file.getLastModified()); link_checksum_ts.append(file.getLastModified()); } } else if (retType.isArray() && VirtualFile.class.isAssignableFrom(retType.getComponentType())) { VirtualFile[] files = (VirtualFile[]) info.getGetterMethod().invoke(targetConfig, (Object[])null); for (int i = 0; files != null && i < files.length; i++) { if (files[i] != null && !files[i].isDirectory()) { if (info.doChecksum()) compile_checksum_ts.append(files[i].getLastModified()); link_checksum_ts.append(files[i].getLastModified()); } } } } // Mxmlc.processConfiguration() derives app-config.xml from the command-line target file argument. // That's why this is necessary... public void calculateChecksum(VirtualFile f) { compile_checksum.append(f.getName()); compile_checksum_ts.append(f.getLastModified()); } // The web tier can use this to provide the timestamps of the dependent files (e.g. remoting-service.xml) // referenced by the service config file. public void calculateChecksum(String name, Long lastModified) { compile_checksum.append(name); compile_checksum_ts.append(lastModified); } // This is to allow the oem api to add archive files to the link checksum. public void calculateLinkChecksum(String name, Long lastModified) { link_checksum.append(name); link_checksum_ts.append(lastModified); } private int calculateChecksum(String str) { byte[] b = null; try { b = str.getBytes("UTF8"); } catch (UnsupportedEncodingException ex) { b = str.getBytes(); } int checksum = 0; //TODO Better checksum algorithm for (int i = 0; i < b.length; i++) { checksum += b[i]; } return checksum; } /** * This value is good for naming the cache file. */ public int checksum() { return calculateChecksum(compile_checksum.toString()); } /** * This value takes timestamps into account and is the actual value embedded in the cache file. */ public int checksum_ts() { return calculateChecksum(compile_checksum.toString() + compile_checksum_ts.toString()); } public int link_checksum_ts() { return calculateChecksum(link_checksum.toString() + link_checksum_ts.toString()); } static public List<String> formatText( String input, int columns ) { ArrayList<String> lines = new ArrayList<String>(); if ((input == null) || (input.length() == 0)) return lines; int current = 0; int lineStart = -1; int lineEnd = -1; int wordStart = -1; int wordEnd = -1; boolean start = true; boolean preserve = true; while (true) { if (current < input.length()) { boolean newline = input.charAt( current ) == '\n'; boolean printable = (preserve && !newline) || !Character.isWhitespace( input.charAt( current ) ); if (start) // find a word { if (printable) { if (lineStart == -1) { lineStart = current; } wordStart = current; start = false; } else { if (newline && lineStart != -1) { lines.add( input.substring( lineStart, current )); lineStart = -1; } else if (newline) { lines.add( "" ); } ++current; } } else // have a word { preserve = false; if (printable) { ++current; } else { wordEnd = current; if (lineEnd == -1) { lineEnd = current; } // two possibilities; if the new word fits in the current line length // without being too many columns, leave on current line. // otherwise, set it as the start of a new line. if (wordEnd - lineStart < columns) { if (newline) { lines.add( input.substring( lineStart, current )); lineStart = -1; lineEnd = -1; wordStart = -1; start = true; preserve = true; ++current; } else { // we have room to add the current word to this line, find new word start = true; lineEnd = current; } } else { // current word pushes things beyond the requested column limit, // dump current text lines.add( input.substring( lineStart, lineEnd ) ); lineStart = wordStart; lineEnd = -1; wordStart = -1; start = true; if (newline) preserve = true; } } } } else // we're done { // a) no line yet, so don't do anything // b) have line and new word would push over edge, need two lines // c) have line and current word fits, need one line // d) only one word and its too long anyway, need one line if (lineStart != -1) // we have a line in progress { wordEnd = current; if (lineEnd == -1) lineEnd = current; if (((wordEnd - lineStart) < columns) // current word fits || (wordEnd == lineEnd)) // or one long word { lineEnd = wordEnd; lines.add( input.substring( lineStart, wordEnd )); } else // didn't fit, multiple words { lines.add( input.substring( lineStart, lineEnd )); lines.add( input.substring( wordStart, wordEnd )); } } break; } } return lines; } }