formatter: load-config option and asformat-config.xml detection
diff --git a/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java b/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
index e6ad9be..29fd7dd 100644
--- a/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
+++ b/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
@@ -36,6 +36,7 @@
 import org.apache.royale.compiler.clients.problems.ProblemQuery;
 import org.apache.royale.compiler.clients.problems.WorkspaceProblemFormatter;
 import org.apache.royale.compiler.common.VersionInfo;
+import org.apache.royale.compiler.config.ConfigurationPathResolver;
 import org.apache.royale.compiler.exceptions.ConfigurationException;
 import org.apache.royale.compiler.filespecs.FileSpecification;
 import org.apache.royale.compiler.internal.config.localization.LocalizationManager;
@@ -311,6 +312,8 @@
 	private boolean configure(String[] args, ProblemQuery problems) {
 		try {
 			Configurator configurator = new Configurator();
+			ConfigurationPathResolver resolver = new ConfigurationPathResolver(System.getProperty("user.dir")); 
+			configurator.setConfigurationPathResolver(resolver);
 			configurator.setConfiguration(args, "files");
 			configuration = configurator.getConfiguration();
 			configBuffer = configurator.getConfigurationBuffer();
@@ -324,7 +327,7 @@
 
 			// Print help if "-help" is present.
 			final List<ConfigurationValue> helpVar = configBuffer.getVar("help");
-			if (helpVar != null || args.length == 0) {
+			if (helpVar != null || (args.length == 0 && configuration.getFiles().size() == 0)) {
 				processHelp(helpVar);
 				return false;
 			}
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/Configuration.java b/formatter/src/main/java/org/apache/royale/formatter/config/Configuration.java
index dd5c0ac..d3ee23a 100644
--- a/formatter/src/main/java/org/apache/royale/formatter/config/Configuration.java
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/Configuration.java
@@ -19,13 +19,16 @@
 
 package org.apache.royale.formatter.config;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.royale.compiler.common.IPathResolver;
 import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.exceptions.ConfigurationException.CannotOpen;
 import org.apache.royale.compiler.internal.config.annotations.Arguments;
 import org.apache.royale.compiler.internal.config.annotations.Config;
 import org.apache.royale.compiler.internal.config.annotations.InfiniteArguments;
@@ -33,6 +36,9 @@
 import org.apache.royale.compiler.problems.DeprecatedConfigurationOptionProblem;
 import org.apache.royale.compiler.problems.ICompilerProblem;
 import org.apache.royale.compiler.problems.RemovedConfigurationOptionProblem;
+import org.apache.royale.utils.FilenameNormalization;
+
+import com.google.common.collect.ImmutableList;
 
 public class Configuration {
 
@@ -49,6 +55,23 @@
         }
         return aliases;
     }
+
+    //
+    // PathResolver
+    //
+    private IPathResolver pathResolver;
+
+    /**
+     * Set a path resolver to resolver files relative to a configuration. Files inside of configuration files are
+     * resolved relative to those configuration files and files on the command line are resolved relative to the root
+     * directory of the compile.
+     * 
+     * @param pathResolver a path resolver for this configuration. May not be null.
+     */
+    public void setPathResolver(IPathResolver pathResolver)
+    {
+        this.pathResolver = pathResolver;
+    }
 	
     /**
      * Collection of fatal and non-fatal configuration problems.
@@ -144,6 +167,31 @@
     }
 
     //
+    // 'load-config' option from CommandLineConfiguration
+    //
+
+    private String configFile = null;
+
+    /**
+     * @return Normalized path to a Flex configuration file.
+     */
+    public String getLoadConfig()
+    {
+        return configFile;
+    }
+
+    /**
+     * Since {@link ConfigurationBuffer} loads the "load-config" files, the value of this configuration option isn't
+     * intersting to the rest part of the compiler.
+     */
+    @Config(allowMultiple = true)
+    @Arguments("filename")
+    public void setLoadConfig(ConfigurationValue cv, String filename) throws ConfigurationException
+    {
+        configFile = resolvePathStrict(filename, cv);   
+    }
+
+    //
     // 'files' option
     //
 
@@ -507,4 +555,104 @@
     {
         this.mxmlInsertNewLineBetweenAttributes = b;
     }
+
+    /**
+     * 
+     * @param path A path to resolve.
+     * @param cv Configuration context.
+     * @return A single normalized resolved file. If the path could be expanded into more than one path, then use
+     *         {@link resolvePathsStrict}
+     * @throws CannotOpen
+     */
+    protected String resolvePathStrict(final String path, final ConfigurationValue cv) throws CannotOpen
+    {
+        return resolvePathStrict(path, cv, false);
+    }
+
+    /**
+     * Resolve a single path. This is a more strict version of {@link #resolvePaths()} in that it throws
+     * {@link CannotOpen} exception when a file path element can't be resolved.
+     * 
+     * @param path A path to resolve.
+     * @param cv Configuration context.
+     * @param returnMissingFiles Determines if the CannotOpen exception is thrown if a file does not exist. Pass true to
+     *        disable exceptions and return files that do not exist. Pass false to throw exceptions.
+     * @return A single normalized resolved file. If the path could be expanded into more than one path, then use
+     *         {@link resolvePathsStrict}.
+     * @throws CannotOpen error
+     * @see #resolvePaths(ImmutableList, ConfigurationValue)
+     */
+    private String resolvePathStrict(final String path, final ConfigurationValue cv, final boolean returnMissingFiles)
+            throws CannotOpen
+    {
+        ImmutableList<String> singletonPath = ImmutableList.of(path);
+        ImmutableList<String> results = resolvePathsStrict(singletonPath, cv, returnMissingFiles);
+        return results.get(0);
+    }
+
+    /**
+     * Resolve a list of paths. This is a more strict version of {@link #resolvePaths()} in that it throws
+     * {@link CannotOpen} exception when a file path element can't be resolved.
+     * 
+     * @param paths A list of paths to resolve.
+     * @param cv Configuration context.
+     * @return A list of normalized resolved file paths.
+     * @throws CannotOpen error
+     * @see #resolvePaths(ImmutableList, ConfigurationValue)
+     */
+    private ImmutableList<String> resolvePathsStrict(final ImmutableList<String> paths, final ConfigurationValue cv)
+            throws CannotOpen
+    {
+        return resolvePathsStrict(paths, cv, false);
+    }
+
+    /**
+     * Resolve a list of paths. This is a more strict version of {@link #resolvePaths()} in that it throws
+     * {@link CannotOpen} exception when a file path element can't be resolved.
+     * 
+     * @param paths A list of paths to resolve.
+     * @param cv Configuration context.
+     * @param returnMissingFiles Determines if the CannotOpen exception is thrown if a file does not exist. Pass true to
+     *        disable exceptions and return files that do not exist. Pass false to throw exceptions.
+     * @return A list of normalized resolved file paths.
+     * @throws CannotOpen error
+     * @see #resolvePaths(ImmutableList, ConfigurationValue)
+     */
+    private ImmutableList<String> resolvePathsStrict(final ImmutableList<String> paths, final ConfigurationValue cv,
+            final boolean returnMissingFiles) throws CannotOpen
+    {
+        assert paths != null : "Expected paths";
+        assert cv != null : "Require ConfigurationValue as context.";
+
+        final ImmutableList.Builder<String> resolvedPathsBuilder = new ImmutableList.Builder<String>();
+        for (String processedPath : paths)
+        {
+            if (cv.getContext() != null)
+            {
+                boolean isAbsolute = new File(processedPath).isAbsolute();
+                if (!isAbsolute)
+                    processedPath = new File(cv.getContext(), processedPath).getAbsolutePath();
+            }
+            if (processedPath.contains("*"))
+            {
+                // if contains wild card, just prove the part before the wild card is valid
+                int c = processedPath.lastIndexOf(File.separator, processedPath.indexOf("*"));
+                if (c != -1)
+                    processedPath = processedPath.substring(0, c);
+            }
+            if (!processedPath.contains(".swc:"))
+            {
+	            final File fileSpec = pathResolver.resolve(processedPath);
+	            if (!returnMissingFiles && !fileSpec.exists())
+	            {
+	                throw new CannotOpen(FilenameNormalization.normalize(processedPath), cv.getVar(), cv.getSource(),
+	                        cv.getLine());
+	            }
+	            resolvedPathsBuilder.add(fileSpec.getAbsolutePath());
+            }
+            else
+                resolvedPathsBuilder.add(processedPath);
+        }
+        return resolvedPathsBuilder.build();
+    }
 }
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/Configurator.java b/formatter/src/main/java/org/apache/royale/formatter/config/Configurator.java
index c2abb48..26ab665 100644
--- a/formatter/src/main/java/org/apache/royale/formatter/config/Configurator.java
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/Configurator.java
@@ -22,17 +22,22 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
+import org.apache.royale.compiler.common.IPathResolver;
 import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.filespecs.FileSpecification;
 import org.apache.royale.compiler.internal.config.localization.LocalizationManager;
 import org.apache.royale.compiler.internal.config.localization.ResourceBundleLocalizer;
 import org.apache.royale.compiler.problems.ConfigurationProblem;
 import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.formatter.internal.config.FileConfigurator;
+import org.apache.royale.formatter.internal.config.SystemPropertyConfigurator;
 import org.apache.royale.utils.FilenameNormalization;
 import org.apache.royale.utils.Trace;
 
@@ -134,17 +139,32 @@
     private Map<String, Object> args, more;
     private String[] extras;
     private String configurationDefaultVariable;
+    private List<String> loadedConfigFiles;
+    private List<String> missingConfigFiles;
     
     private Map<String, String> tokens;
     
     private boolean isConfigurationDirty;
     private boolean configurationSuccess;
     protected Collection<ICompilerProblem> configurationProblems;
+    private IPathResolver configurationPathResolver;
 
     // 
     // IConfigurator related methods
     //
 
+    public List<String> getLoadedConfigurationFiles()
+    {
+        return loadedConfigFiles != null ? loadedConfigFiles :
+            Collections.<String>emptyList();
+    }
+
+    public List<String> getMissingConfigurationFiles()
+    {
+        return missingConfigFiles != null ? missingConfigFiles :
+            Collections.<String>emptyList();
+    }
+
     public Collection<ICompilerProblem> validateConfiguration(String[] args)
     {
         if (args == null)
@@ -178,6 +198,14 @@
         return problems;
     }
 
+    public void setConfigurationPathResolver(IPathResolver pathResolver)
+    {
+        if (pathResolver == null)
+            throw new NullPointerException("pathResolver may not be null");
+
+        this.configurationPathResolver = pathResolver;
+    }
+
     public Configuration getConfiguration()
     {
         processConfiguration();
@@ -238,6 +266,8 @@
         // Create a clean configuration and configuration buffer
         configuration = createConfiguration();
         cfgbuf = createConfigurationBuffer(configuration.getClass());
+        assert configurationPathResolver != null : "No configuration path resolver was set.";
+        configuration.setPathResolver(configurationPathResolver);
     }
 
     /**
@@ -324,6 +354,13 @@
             final List<ConfigurationValue> helpVar = cfgbuf.getVar("help");
             if (helpVar != null)
                 return false;
+
+            // Load configurations from files.
+            if (!loadConfig())
+                success = false;
+
+            if (!loadLocalConfig())
+                success = false;
             
             // The command line needs to take precedence over all defaults and config files.
             // By simply re-merging the command line back on top,
@@ -357,6 +394,125 @@
     }
 
     /**
+     * Load configuration XML file specified in {@code -load-config} option on
+     * command-line.
+     * 
+     * @return true if successful, false otherwise.
+     */
+    protected boolean loadConfig()
+    {
+        boolean success = true;
+        
+        List<ConfigurationValue> configs;
+        try
+        {
+            configs = cfgbuf.peekConfigurationVar("load-config");
+            if (configs != null)
+            {
+                for (ConfigurationValue cv : configs)
+                {
+                    for (String path : cv.getArgs())
+                    {
+                        File configFile = configurationPathResolver.resolve(path);
+                        if (!configFile.exists())
+                        {
+                            success = false;
+                            if (missingConfigFiles == null)
+                                missingConfigFiles = new ArrayList<String>();
+                            
+                            missingConfigFiles.add(path);
+                        }
+                        else 
+                        {
+                           if (!loadConfigFromFile(
+                                    cfgbuf,
+                                    configFile,
+                                    new File(configFile.getPath()).getParent(),
+                                    "asformat-config",
+                                    false))
+                           {
+                               success = false;
+                           }
+                        }
+                    }
+                }
+            }
+        }
+        catch (ConfigurationException e)
+        {
+            reportConfigurationException(e);
+            success = false;
+        }
+
+        return success;
+    }
+
+    /**
+     * Load project specific configuration. The configuration XML file is at the
+     * project root with naming convention of asformat-config.xml.
+     * 
+     * @return true if successful, false otherwise.
+     */
+    protected boolean loadLocalConfig()
+    {
+        boolean success = true;
+        
+        String project = "asformat-config.xml";
+        File projectFile = configurationPathResolver.resolve(project);
+        if (projectFile.exists())
+        {
+            if (!loadConfigFromFile(
+                    cfgbuf,
+                    projectFile,
+                    new File(project).getParent(),
+                    "asformat-config",
+                    false))
+            {
+                success = false;
+            }
+        }
+        
+        return success;
+    }
+
+    /**
+     * Load a configuration from file. {@code FileConfigurator.load()} is
+     * wrapped in this method because we want to print a message after loading
+     * using MXMLC#println(String).
+     * 
+     * @return true if successful, false otherwise.
+     */
+    protected final boolean loadConfigFromFile(final ConfigurationBuffer buffer,
+                                          final File fileSpec,
+                                          final String context,
+                                          final String rootElement,
+                                          final boolean ignoreUnknownItems)
+    {
+        boolean success = true;
+        
+        try
+        {
+            FileConfigurator.load(buffer, 
+                    new FileSpecification(fileSpec.getAbsolutePath()),
+                    context, rootElement, ignoreUnknownItems);
+        }
+        catch (ConfigurationException e)
+        {
+            // record exception
+            reportConfigurationException(e);
+            success = false;
+            
+        }
+
+        if (loadedConfigFiles == null)
+            loadedConfigFiles = new ArrayList<String>();
+        
+        loadedConfigFiles.add(fileSpec.getPath());
+        
+        return success;
+    }
+
+    /**
      * Convert conifguration exceptions to problems and collect them for 
      * reporting.
      * 
diff --git a/formatter/src/main/java/org/apache/royale/formatter/internal/config/FileConfigurator.java b/formatter/src/main/java/org/apache/royale/formatter/internal/config/FileConfigurator.java
new file mode 100644
index 0000000..79fc447
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/internal/config/FileConfigurator.java
@@ -0,0 +1,680 @@
+/*
+ *
+ *  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 org.apache.royale.formatter.internal.config;
+
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.commons.io.IOUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.royale.formatter.config.ConfigurationBuffer;
+import org.apache.royale.formatter.config.ConfigurationInfo;
+import org.apache.royale.formatter.config.ConfigurationValue;
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.filespecs.IFileSpecification;
+import org.apache.royale.compiler.internal.config.localization.LocalizationManager;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * A utility class, which is used to parse an XML file of configuration options
+ * and populate a ConfigurationBuffer. A counterpart of CommandLineConfigurator
+ * and SystemPropertyConfigurator.
+ * 
+ * @see <a href="http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf67670-7ff2.html">Configuration file syntax</a>
+ */
+public class FileConfigurator
+{
+
+    public static class SAXConfigurationException extends SAXParseException
+    {
+        private static final long serialVersionUID = -3388781933743434302L;
+
+        SAXConfigurationException(ConfigurationException e, Locator locator)
+        {
+            super(null, locator); // ?
+            this.innerException = e;
+        }
+
+        public ConfigurationException innerException;
+    }
+
+    /**
+     * Load configuration XML file into a {@link ConfigurationBuffer} object.
+     * 
+     * @param buffer result {@link ConfigurationBuffer} object.
+     * @param fileSpec configuration XML file.
+     * @param context path context used for resolving relative paths in the
+     * configuration options.
+     * @param rootElement expected root element of the XML DOM tree.
+     * @param ignoreUnknownItems if false, unknown option will cause exception.
+     * @throws ConfigurationException error.
+     */
+    public static void load(
+            final ConfigurationBuffer buffer,
+            final IFileSpecification fileSpec,
+            final String context,
+            final String rootElement,
+            boolean ignoreUnknownItems)
+            throws ConfigurationException
+    {
+        final String path = fileSpec.getPath();
+        final Handler h = new Handler(buffer, path, context, rootElement, ignoreUnknownItems);
+        final SAXParserFactory factory = SAXParserFactory.newInstance();
+        Reader reader = null;
+        try
+        {
+            reader = fileSpec.createReader();
+            final SAXParser parser = factory.newSAXParser();
+            final InputSource source = new InputSource(reader);
+            parser.parse(source, h);
+        }
+        catch (SAXConfigurationException e)
+        {
+            throw e.innerException;
+        }
+        catch (SAXParseException e)
+        {
+            throw new ConfigurationException.OtherThrowable(e, null, path, e.getLineNumber());
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException.OtherThrowable(e, null, path, -1);
+        }
+        finally
+        {
+            IOUtils.closeQuietly(reader);
+        }
+    }
+
+    /**
+     * SAX handler for configuration XML.
+     */
+    private static class Handler extends DefaultHandler
+    {
+        private static final String ATTRIBUTE_APPEND = "append";
+
+        public Handler(ConfigurationBuffer buffer,
+                       String source,
+                       String contextPath,
+                       String rootElement,
+                       boolean ignoreUnknownItems)
+        {
+            this.cfgbuf = buffer;
+            this.source = source;
+            this.contextPath = contextPath;
+            this.rootElement = rootElement;
+            this.ignoreUnknownItems = ignoreUnknownItems;
+        }
+
+        private final Stack<ParseContext> contextStack = new Stack<ParseContext>();
+        private final ConfigurationBuffer cfgbuf;
+        private final String source;
+        private final String contextPath;
+        private final String rootElement;
+        private final boolean ignoreUnknownItems;
+        private final StringBuilder text = new StringBuilder();
+        private Locator locator;
+
+        @Override
+        public void startElement(final String uri, final String localName, final String qname, final Attributes attributes) throws SAXException
+        {
+            // Verify and initialize the context stack at root element.
+            if (contextStack.size() == 0)
+            {
+                if (!qname.equals(rootElement))
+                {
+                    if (!qname.equals("flex-config"))
+                    {
+                        throw new SAXConfigurationException(
+                            new ConfigurationException.IncorrectElement(rootElement, qname, this.source, locator.getLineNumber()),
+                            locator);
+                    }
+                }
+                final ParseContext ctx = new ParseContext();
+                contextStack.push(ctx);
+                return;
+            }
+
+            final ParseContext ctx = contextStack.peek();
+
+            if (ctx.ignore)
+            {
+                // ignore starting new elements
+                return;
+            }
+
+            if (text.length() > 0)
+            {
+                // Only leave nodes can have CDATA as option values.
+                throw new SAXConfigurationException(
+                        new ConfigurationException.UnexpectedCDATA(this.source, locator.getLineNumber()),
+                        locator);
+            }
+
+            final String fullname = ConfigurationBuffer.varname(qname, ctx.base);
+
+            if (ctx.item != null)
+            {
+                throw new SAXConfigurationException(
+                        new ConfigurationException.UnexpectedElement(qname, contextPath, locator.getLineNumber()),
+                        locator);
+            }
+            else if (ctx.var != null)
+            {
+                // we're setting values for a variable
+
+                if (ctx.varArgCount == 1)
+                {
+                    // oops, we weren't expecting more than one value!
+
+                    throw new SAXConfigurationException(
+                            new ConfigurationException.UnexpectedElement(qname, source, locator.getLineNumber()),
+                            locator);
+                }
+                ctx.item = qname;
+            }
+            else if (cfgbuf.isValidVar(fullname))
+            {
+                ctx.var = fullname;
+                ctx.varArgCount = cfgbuf.getVarArgCount(ctx.var);
+                ctx.append = false;
+                final String append = attributes.getValue(ATTRIBUTE_APPEND);
+                if (append != null)
+                {
+                    if (append.equalsIgnoreCase("true") || append.equalsIgnoreCase("false"))
+                        ctx.append = Boolean.valueOf(append).booleanValue();
+                    else
+                        throw new SAXConfigurationException(
+                                new ConfigurationException.BadAppendValue(
+                                        ctx.var,
+                                        source,
+                                        locator.getLineNumber()),
+                                locator);
+                }
+            }
+            else if (isSubTree(fullname))
+            {
+                final ParseContext newctx = new ParseContext();
+                newctx.base = fullname;
+                contextStack.push(newctx);
+            }
+            else
+            {
+                if (ignoreUnknownItems)
+                {
+                    // push a new context and ignore everything until we get the end 
+                    // of this element.
+                    ParseContext newctx = new ParseContext();
+                    newctx.item = qname;
+                    newctx.ignore = true;
+                    contextStack.push(newctx);
+                    return;
+                }
+                System.err.println("Unknown tag:" + fullname);
+                throw new SAXConfigurationException(
+                        new ConfigurationException.UnknownVariable(
+                                                    fullname, source, locator.getLineNumber()),
+                        locator);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qname) throws SAXException
+        {
+            final ParseContext ctx = contextStack.peek();
+
+            if (ctx.ignore)
+            {
+                // if found the matching end element, then pop the context and stop ignoring input
+                if (ctx.item.equals(qname))
+                {
+                    contextStack.pop();
+                    text.setLength(0); // ignore any text read
+                }
+
+                return;
+            }
+
+            // There are four possible states here;
+            // 1. localname==rootElement -> end of file, pop, we're done
+            // 2. localname==itemElement -> finished gathering text, push onto arglist
+            // 2. var is set -> set the var to the argList, pop
+            // 3. var is null -> we're finishing a child config, pop
+
+            if (qname.equals(rootElement))
+            {
+                // Finished with the file!
+            }
+            else if (ctx.item != null)
+            {
+                // Finished with the current item.
+                final ParseValue v = new ParseValue();
+                v.name = qname;
+                v.value = text.toString();
+                v.line = locator.getLineNumber();
+                ctx.argList.add(v);
+                text.setLength(0);
+                ctx.item = null;
+            }
+            else if (ctx.var != null)
+            {
+                if ((ctx.varArgCount > 1) && (ctx.argList.size() == 0))
+                {
+                    throw new SAXConfigurationException(
+                            new ConfigurationException.IncorrectArgumentCount(ctx.varArgCount, 0,
+                                                                               ctx.var, source, locator.getLineNumber()),
+                            locator);
+                }
+                if (ctx.varArgCount == 1)
+                {
+                    ParseValue v = new ParseValue();
+                    v.name = null;
+                    v.value = text.toString();
+                    v.line = locator.getLineNumber();
+                    ctx.argList.add(v);
+                    text.setLength(0);
+                }
+                else
+                {
+                    if (text.length() > 0)
+                    {
+                        // "unexpected CDATA encountered, " + ctx.var + " requires named arguments.", locator );
+                        throw new SAXConfigurationException(
+                                new ConfigurationException.UnexpectedCDATA(source, locator.getLineNumber()),
+                                locator);
+
+                    }
+                }
+                // Finished with the current var, save the current list
+                try
+                {
+                    setVar(ctx.var, ctx.argList, locator.getLineNumber(), ctx.append);
+                    ctx.var = null;
+                    ctx.argList.clear();
+                    ctx.item = null;
+                    ctx.append = false;
+                }
+                catch (ConfigurationException e)
+                {
+                    throw new SAXConfigurationException(e, locator);
+                }
+            }
+            else
+            {
+                // done with a child config
+                contextStack.pop();
+            }
+        }
+
+        public void setVar(String var, List<ParseValue> argList, int line, boolean append) throws ConfigurationException
+        {
+            int varArgCount = cfgbuf.getVarArgCount(var);
+
+            Map<String, String> items = new HashMap<String, String>();
+
+            boolean byName = (varArgCount > 1);
+
+            if (byName)
+            {
+                for (Iterator<ParseValue> it = argList.iterator(); it.hasNext();)
+                {
+                    ParseValue v = it.next();
+
+                    if (items.containsKey(v.name))
+                    {
+                        byName = false; // can't support byName, duplicate item name!
+                        break;
+                    }
+                    else
+                    {
+                        items.put(v.name, v.value);
+                    }
+                }
+            }
+            List<String> args = new LinkedList<String>();
+
+            if (byName)
+            {
+                int argc = 0;
+
+                while (args.size() < items.size())
+                {
+                    String name = cfgbuf.getVarArgName(var, argc++);
+                    String val = items.get(name);
+                    if (val == null)
+                    {
+                        throw new ConfigurationException.MissingArgument(name, var, source, line);
+                    }
+                    args.add(val);
+                }
+            }
+            else
+            {
+                Iterator<ParseValue> it = argList.iterator();
+                int argc = 0;
+                while (it.hasNext())
+                {
+                    ParseValue v = it.next();
+                    String name = cfgbuf.getVarArgName(var, argc++);
+                    if ((v.name != null) && !name.equals(v.name))
+                    {
+                        throw new ConfigurationException.UnexpectedArgument(name, v.name, var, source, v.line);
+                    }
+                    args.add(v.value);
+                }
+            }
+            cfgbuf.setVar(var, args, source, line, contextPath, append);
+        }
+
+        @Override
+        public void characters(char ch[], int start, int length)
+        {
+            String chars = new String(ch, start, length).trim();
+            text.append(chars);
+        }
+
+        @Override
+        public void setDocumentLocator(Locator locator)
+        {
+            this.locator = locator;
+        }
+    }
+
+    private static class ParseContext
+    {
+        ParseContext()
+        {
+            this.base = null;
+            this.var = null;
+            this.varArgCount = -2;
+            this.argList = new LinkedList<ParseValue>();
+            this.append = false;
+            this.ignore = false;
+        }
+
+        public String var;
+        public String base;
+        public String item;
+        public int varArgCount;
+        public boolean append;
+        public List<ParseValue> argList;
+        public boolean ignore; // ignore this variable, do not put in config buffer
+    }
+
+    private static class ParseValue
+    {
+        public String name;
+        public String value;
+        public int line;
+    }
+
+    private static class FormatNode
+    {
+        public String fullname;
+        public String shortname;
+        public ConfigurationInfo info;
+        public List<ConfigurationValue> values;
+
+        public TreeMap<String, FormatNode> children; // only for configs
+    }
+
+    static final String pad = "   ";
+
+    /**
+     * These XML nodes can have subtrees of configurations.
+     */
+    protected static final ImmutableSet<String> VALID_SUBTREE_TAG = ImmutableSet.of(
+            "compiler",
+            "compiler.namespaces",
+            "compiler.js-namespaces",
+            "compiler.fonts",
+            "compiler.fonts.languages",
+            "compiler.mxml",
+            "compiler.mxml.imports",
+            "metadata",
+            "licenses",
+            "frames",
+            "runtime-shared-library-settings");
+
+    /**
+     * @param fullname
+     * @return
+     */
+    private static boolean isSubTree(String fullname)
+    {
+        return VALID_SUBTREE_TAG.contains(fullname);
+    }
+
+    private static 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();
+    }
+
+    private static String formatBuffer1(ConfigurationBuffer cfgbuf,
+                                         FormatNode node,
+                                         String indent,
+                                         LocalizationManager lmgr,
+                                         String prefix)
+    {
+        StringBuilder buf = new StringBuilder(1024);
+
+        buf.append(indent + "<" + node.shortname + ">\n");
+        if (node.children != null)
+        {
+            for (final String key : node.children.keySet())
+            {
+                final FormatNode child = node.children.get(key);
+
+                if (child.children != null) // its a config
+                {
+                    buf.append(formatBuffer1(cfgbuf, child, indent + pad, lmgr, prefix));
+                }
+                else
+                {
+                    String description = lmgr.getLocalizedTextString(prefix + "." + child.fullname);
+
+                    if (description != null)
+                        buf.append(indent + pad + "<!-- " + child.fullname + ": " + description + "-->\n");
+
+                    if ((child.values == null) || !child.info.isDisplayed())
+                    {
+                        boolean newline = false;
+                        buf.append(indent + pad + "<!-- " + child.fullname + " usage:\n");
+                        buf.append(indent + pad + "<" + child.shortname + ">");
+
+                        int i = 0;
+                        while (true)
+                        {
+                            if (child.info.getArgCount() == 1)
+                            {
+                                buf.append(child.info.getArgName(i));
+                                break;
+                            }
+                            else
+                            {
+                                buf.append("\n" + indent + pad + pad + "<" + child.info.getArgName(i) + ">" + classToArgName(child.info.getArgType(i)) + "</" + child.info.getArgName(i) + ">");
+                                newline = true;
+                            }
+                            if (child.info.getArgCount() == -1)
+                            {
+                                if (i > 0)
+                                {
+                                    // stop iterating thru arguments when an arg name
+                                    // matches a previously used arg name.
+                                    boolean found = false; // true if found argName in the arg list
+                                    String argName = child.info.getArgName(i + 1);
+                                    for (int j = i; j >= 0; j--)
+                                    {
+                                        if (child.info.getArgName(j).equals(argName))
+                                        {
+                                            found = true;
+                                            break;
+                                        }
+                                    }
+                                    if (found)
+                                    {
+                                        break;
+                                    }
+                                }
+                            }
+                            else if (i >= child.info.getArgCount())
+                            {
+                                break;
+                            }
+                            ++i;
+                        }
+                        if (newline)
+                            buf.append("\n" + indent + pad);
+
+                        buf.append("</" + child.shortname + ">\n");
+                        buf.append(indent + pad + "-->\n");
+                    }
+                    else
+                    {
+                        // var may be set multiple times...
+                        boolean newline = false;
+                        for (final ConfigurationValue cv : child.values)
+                        {
+                            buf.append(indent + pad + "<" + child.shortname + ">");
+
+                            int argCount = child.info.getArgCount();
+                            // var may have multiple values...
+                            int argc = 0;
+                            for (final String arg : cv.getArgs())
+                            {
+                                if (argCount == 1)
+                                {
+                                    buf.append(arg);
+                                    break;
+                                }
+                                else
+                                {
+                                    String argname = child.info.getArgName(argc++);
+                                    newline = true;
+                                    buf.append("\n" + indent + pad + pad + "<" + argname + ">" + arg + "</" + argname + ">");
+                                }
+                            }
+                            if (newline)
+                                buf.append("\n" + indent + pad);
+                            buf.append("</" + child.shortname + ">\n");
+                        }
+                    }
+                }
+            }
+        }
+        buf.append(indent + "</" + node.shortname + ">\n");
+
+        return buf.toString();
+    }
+
+    private static void addNode(ConfigurationBuffer cfgbuf, String var, FormatNode root)
+    {
+        String name = null;
+        StringTokenizer t = new StringTokenizer(var, ".");
+
+        FormatNode current = root;
+
+        while (t.hasMoreTokens())
+        {
+            String token = t.nextToken();
+
+            if (name == null)
+                name = token;
+            else
+                name += "." + token;
+
+            if (current.children == null)
+                current.children = new TreeMap<String, FormatNode>();
+
+            if (isSubTree(name))
+            {
+                if (!current.children.containsKey(token))
+                {
+                    FormatNode node = new FormatNode();
+                    node.fullname = name;
+                    node.shortname = token;
+                    node.children = new TreeMap<String, FormatNode>();
+                    current.children.put(token, node);
+                    current = node;
+                }
+                else
+                {
+                    current = current.children.get(token);
+                }
+            }
+            else if (cfgbuf.isValidVar(name))
+            {
+                FormatNode node = new FormatNode();
+                node.fullname = name;
+                node.shortname = token;
+                node.info = cfgbuf.getInfo(name);
+                node.values = cfgbuf.getVar(name);
+                current.children.put(token, node);
+            }
+        }
+    }
+
+    public static String formatBuffer(ConfigurationBuffer cfgbuf,
+                                       String rootElement,
+                                       LocalizationManager lmgr,
+                                       String prefix)
+    {
+        FormatNode root = new FormatNode();
+        root.shortname = rootElement;
+        for (final String var : cfgbuf.getVars())
+        {
+            // if var is a 'hidden' or a 'removed' parameter, don't dump.
+            ConfigurationInfo info = cfgbuf.getInfo(var);
+            if (info != null && (info.isHidden() || info.isRemoved() || !info.isDisplayed()))
+            {
+                continue;
+            }
+            addNode(cfgbuf, var, root);
+        }
+
+        return formatBuffer1(cfgbuf, root, "", lmgr, prefix);
+    }
+
+    public static String formatBuffer(ConfigurationBuffer cfgbuf, String rootElement)
+    {
+        return formatBuffer(cfgbuf, rootElement, null, null);
+    }
+}
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/SystemPropertyConfigurator.java b/formatter/src/main/java/org/apache/royale/formatter/internal/config/SystemPropertyConfigurator.java
similarity index 95%
rename from formatter/src/main/java/org/apache/royale/formatter/config/SystemPropertyConfigurator.java
rename to formatter/src/main/java/org/apache/royale/formatter/internal/config/SystemPropertyConfigurator.java
index b74fae0..d32b4c4 100644
--- a/formatter/src/main/java/org/apache/royale/formatter/config/SystemPropertyConfigurator.java
+++ b/formatter/src/main/java/org/apache/royale/formatter/internal/config/SystemPropertyConfigurator.java
@@ -17,7 +17,7 @@
  *
  */
 
-package org.apache.royale.formatter.config;
+package org.apache.royale.formatter.internal.config;
 
 import java.util.Properties;
 import java.util.Enumeration;
@@ -26,6 +26,7 @@
 import java.util.StringTokenizer;
 
 import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.formatter.config.ConfigurationBuffer;
 
 /**
  * A utility class, which is used to load configuration options via