You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by jo...@apache.org on 2021/09/20 21:24:56 UTC

[royale-compiler] 01/05: formatter: command line configuration

This is an automated email from the ASF dual-hosted git repository.

joshtynjala pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git

commit 1a9333bca06dc74bdf79fbb906c47850fc0999f5
Author: Josh Tynjala <jo...@apache.org>
AuthorDate: Mon Sep 20 11:25:38 2021 -0700

    formatter: command line configuration
---
 formatter/build.xml                                |   11 +-
 .../org/apache/royale/formatter/FORMATTER.java     |   90 +-
 .../formatter/config/CommandLineConfigurator.java  |  607 +++++++++
 .../royale/formatter/config/Configuration.java     |  204 +++
 .../formatter/config/ConfigurationBuffer.java      | 1346 ++++++++++++++++++++
 .../royale/formatter/config/ConfigurationInfo.java |  473 +++++++
 .../formatter/config/ConfigurationValue.java       |  109 ++
 .../royale/formatter/config/Configurator.java      |  683 ++++++++++
 .../config/IFormatterSettingsConstants.java        |   24 +
 .../config/SystemPropertyConfigurator.java         |   82 ++
 10 files changed, 3595 insertions(+), 34 deletions(-)

diff --git a/formatter/build.xml b/formatter/build.xml
index 1243567..93e6bc8 100644
--- a/formatter/build.xml
+++ b/formatter/build.xml
@@ -115,9 +115,14 @@
     
     <target name="jar-test" >
         <echo>using formatter.jar from ${sdk}</echo>
-        <java jar="${sdk}/formatter.jar" fork="true"
-            failonerror="true">
-        </java>
+        <java jar="${sdk}/formatter.jar" fork="true" resultproperty="formatter.result"/>
+        <fail message="Starting Failed">
+            <condition>
+                <not>
+                    <equals arg1="${formatter.result}" arg2="1"/>
+                </not>
+            </condition>
+        </fail>
     </target>
     
     <!--
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 ae80ce7..cb33693 100644
--- a/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
+++ b/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
@@ -29,6 +29,9 @@ import java.util.List;
 import java.util.Scanner;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.royale.compiler.clients.problems.ProblemFormatter;
+import org.apache.royale.compiler.clients.problems.ProblemPrinter;
+import org.apache.royale.compiler.clients.problems.ProblemQuery;
 import org.apache.royale.compiler.common.VersionInfo;
 import org.apache.royale.compiler.exceptions.ConfigurationException;
 import org.apache.royale.compiler.internal.parsing.as.ASParser;
@@ -43,8 +46,12 @@ import org.apache.royale.compiler.internal.parsing.as.StreamingASTokenizer;
 import org.apache.royale.compiler.internal.tree.as.FileNode;
 import org.apache.royale.compiler.internal.workspaces.Workspace;
 import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.ConfigurationProblem;
 import org.apache.royale.compiler.problems.ICompilerProblem;
 import org.apache.royale.compiler.problems.UnexpectedExceptionProblem;
+import org.apache.royale.formatter.config.Configuration;
+import org.apache.royale.formatter.config.ConfigurationBuffer;
+import org.apache.royale.formatter.config.Configurator;
 import org.apache.royale.utils.FilenameNormalization;
 
 /**
@@ -102,12 +109,14 @@ class FORMATTER {
 	public boolean ignoreProblems = false;
 	public boolean collapseEmptyBlocks = false;
 
+	private ProblemQuery problems;
 	private List<File> inputFiles = new ArrayList<File>();
 	private boolean writeBackToInputFiles = false;
 	private boolean listChangedFiles = false;
 
 	public int execute(String[] args) {
 		ExitCode exitCode = ExitCode.SUCCESS;
+		problems = new ProblemQuery();
 
 		try {
 			boolean continueFormatting = configure(args);
@@ -151,13 +160,19 @@ class FORMATTER {
 						}
 					}
 				}
+			} else if (problems.hasFilteredProblems()) {
+				exitCode = ExitCode.FAILED_WITH_CONFIG_PROBLEMS;
+			} else {
+				exitCode = ExitCode.PRINT_HELP;
 			}
-		} catch (ConfigurationException e) {
-			System.err.println(e.getMessage());
-			exitCode = ExitCode.FAILED_WITH_CONFIG_PROBLEMS;
 		} catch (Exception e) {
 			System.err.println(e.getMessage());
 			exitCode = ExitCode.FAILED_WITH_EXCEPTIONS;
+		} finally {
+			if (problems.hasFilteredProblems()) {
+				final ProblemPrinter printer = new ProblemPrinter(ProblemFormatter.DEFAULT_FORMATTER);
+				printer.printProblems(problems.getFilteredProblems());
+			}
 		}
 		return exitCode.code;
 	}
@@ -193,28 +208,33 @@ class FORMATTER {
 		return formatText(text, null);
 	}
 
-	private boolean configure(String[] args) throws ConfigurationException {
+	private boolean configure(String[] args) {
 		if (args.length == 0) {
 			printHelp();
 			return false;
 		}
-		for (int i = 0; i < args.length; i++) {
-			String arg = args[i];
-			if (arg.charAt(0) == '-') {
-				if (arg.equals("-write") || arg.equals("-w")) {
-					writeBackToInputFiles = true;
-				} else if (arg.equals("-list") || arg.equals("-l")) {
-					listChangedFiles = true;
-				} else if (arg.equals("-help") || arg.equals("-h")) {
-					printHelp();
-					return false;
-				} else {
-					throw new ConfigurationException("Unknown command-line argument: " + arg, null, -1);
-				}
-			} else {
-				File inputFile = new File(arg);
+		try {
+			problems = new ProblemQuery();
+
+			Configurator configurator = new Configurator();
+			configurator.setConfiguration(args, "files");
+			Configuration config = configurator.getConfiguration();
+			ConfigurationBuffer configBuffer = configurator.getConfigurationBuffer();
+
+			problems.addAll(configurator.getConfigurationProblems());
+
+			if (configBuffer.getVar("version") != null)
+				return false;
+
+			if (problems.hasErrors())
+				return false;
+
+			writeBackToInputFiles = config.getWriteFiles();
+			listChangedFiles = config.getListFiles();
+			for (String filePath : config.getFiles()) {
+				File inputFile = new File(filePath);
 				if (!inputFile.exists()) {
-					throw new ConfigurationException("Input file does not exist: " + arg, null, -1);
+					throw new ConfigurationException("Input file does not exist: " + filePath, null, -1);
 				}
 				if (inputFile.isDirectory()) {
 					addDirectory(inputFile);
@@ -222,21 +242,29 @@ class FORMATTER {
 					inputFiles.add(inputFile);
 				}
 			}
-		}
-		if (inputFiles.size() == 0 && writeBackToInputFiles) {
-			throw new ConfigurationException("Cannot use -w with standard input", null, -1);
-		}
-		if (writeBackToInputFiles) {
-			if (inputFiles.size() == 0) {
-				throw new ConfigurationException("Cannot use -w with standard input", null, -1);
+			if (inputFiles.size() == 0 && listChangedFiles) {
+				throw new ConfigurationException("Cannot use -list-files with standard input", null, -1);
 			}
-			for (File inputFile : inputFiles) {
-				if (!inputFile.canWrite()) {
-					throw new ConfigurationException("File is read-only: " + inputFile.getPath(), null, -1);
+			if (writeBackToInputFiles) {
+				if (inputFiles.size() == 0) {
+					throw new ConfigurationException("Cannot use -write-files with standard input", null, -1);
+				}
+				for (File inputFile : inputFiles) {
+					if (!inputFile.canWrite()) {
+						throw new ConfigurationException("File is read-only: " + inputFile.getPath(), null, -1);
+					}
 				}
 			}
+			return true;
+		} catch (ConfigurationException e) {
+			final ICompilerProblem problem = new ConfigurationProblem(e);
+			problems.add(problem);
+			return false;
+		} catch (Exception e) {
+			final ICompilerProblem problem = new ConfigurationProblem(null, -1, -1, -1, -1, e.getMessage());
+			problems.add(problem);
+			return false;
 		}
-		return true;
 	}
 
 	private void addDirectory(File inputFile) {
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/CommandLineConfigurator.java b/formatter/src/main/java/org/apache/royale/formatter/config/CommandLineConfigurator.java
new file mode 100644
index 0000000..a42960f
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/CommandLineConfigurator.java
@@ -0,0 +1,607 @@
+/*
+ *
+ *  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.config;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.TreeSet;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.File;
+
+import com.google.common.base.Joiner;
+
+import org.apache.royale.compiler.Messages;
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.internal.config.localization.LocalizationManager;
+
+/**
+ * A utility class, which is used to parse an array of command line args and
+ * populate a ConfigurationBuffer. It also contains some associated methods like
+ * brief() and usage(). A counterpart of FileConfigurator and
+ * SystemPropertyConfigurator.
+ */
+public class CommandLineConfigurator
+{
+    public static final String SOURCE_COMMAND_LINE = "command line";
+
+    /**
+     * parse - buffer up configuration vals from the command line
+     * 
+     * @param buffer the configuration buffer to hold the results
+     * @param defaultvar the variable name where the trailing loose args go
+     * @param args the command line
+     */
+    public static void parse(final ConfigurationBuffer buffer,
+                              final String defaultvar,
+                              final String[] args)
+            throws ConfigurationException
+    {
+        // "no-default-arg" means the application does not have a default var.
+        assert defaultvar == null || buffer.isValidVar(defaultvar) || "no-default-arg".equals(defaultvar) : "coding error: config must provide default var " + defaultvar;
+
+        Map<String, String> aliases = getAliases(buffer);
+        final int START = 1;
+        final int ARGS = 2;
+        final int EXEC = 3;
+        final int DONE = 4;
+
+        int i = 0, iStart = 0, iEnd = 0;
+        String var = null;
+        int varArgCount = -2;
+        List<String> argList = new LinkedList<String>();
+        Set<String> vars = new HashSet<String>();
+        boolean append = false;
+        boolean dash = true;
+
+        int mode = START;
+
+        while (mode != DONE)
+        {
+            switch (mode)
+            {
+                case START:
+                {
+                    iStart = i;
+
+                    if (args.length == i)
+                    {
+                        mode = DONE;
+                        break;
+                    }
+                    // expect -var, --, or the beginning of default args
+
+                    mode = ARGS;
+                    varArgCount = -2;
+
+                    if (args[i].equals("--"))
+                    {
+                        dash = false;
+                        if (defaultvar != null)
+                            var = defaultvar;
+                        else
+                            mode = START;
+                        ++i;
+                    }
+                    else if (dash && args[i].startsWith("+"))
+                    {
+                        String token = null;
+                        int c = (args[i].length() > 1 && args[i].charAt(1) == '+') ? 2 : 1; // gnu-style?
+
+                        int equals = args[i].indexOf('=');
+                        String rest = null;
+                        if (equals != -1)
+                        {
+                            rest = args[i].substring(equals + 1);
+                            token = args[i++].substring(c, equals);
+                        }
+                        else
+                        {
+                            token = args[i++].substring(c);
+                        }
+                        if (equals != -1)
+                        {
+                            iEnd = i;
+                            buffer.setToken(token, rest);
+                            buffer.addPosition(token, iStart, iEnd);
+                        }
+                        else
+                        {
+                            if (i == args.length)
+                            {
+                                throw new ConfigurationException.Token(ConfigurationException.Token.INSUFFICIENT_ARGS,
+                                                                        token, var, source, -1);
+                            }
+                            rest = args[i++];
+                            iEnd = i;
+                            buffer.setToken(token, rest);
+                            buffer.addPosition(token, iStart, iEnd);
+                        }
+                        mode = START;
+                        break;
+                    }
+                    else if (dash && isAnArgument(args[i]))
+                    {
+                        int c = (args[i].length() > 1 && args[i].charAt(1) == '-') ? 2 : 1; // gnu-style?
+
+                        int plusequals = args[i].indexOf("+=");
+                        int equals = args[i].indexOf('=');
+                        String rest = null;
+                        if (plusequals != -1)
+                        {
+                            rest = args[i].substring(plusequals + 2);
+                            var = args[i++].substring(c, plusequals);
+                            append = true;
+                        }
+                        else if (equals != -1)
+                        {
+                            rest = args[i].substring(equals + 1);
+                            var = args[i++].substring(c, equals);
+                        }
+                        else
+                        {
+                            var = args[i++].substring(c);
+                        }
+
+                        if (aliases.containsKey(var))
+                            var = aliases.get(var);
+
+                        if (!buffer.isValidVar(var))
+                        {
+                            throw new ConfigurationException.UnknownVariable(var, source, -1);
+                        }
+
+                        if (equals != -1)
+                        {
+                            if ((rest == null) || (rest.length() == 0))
+                            {
+                                varArgCount = -1;
+                                mode = EXEC;
+                            }
+                            else
+                            {
+                                String seps = null;
+                                if (buffer.getInfo(var).isPath())
+                                {
+                                    seps = "[," + File.pathSeparatorChar + "]";
+                                }
+                                else
+                                {
+                                    seps = ",";
+                                }
+
+                                String[] tokens = rest.split(seps);
+                                argList.addAll(Arrays.asList(tokens));
+                                varArgCount = buffer.getVarArgCount(var);
+                                mode = EXEC;
+                            }
+                        }
+
+                    }
+                    else
+                    {
+                        // asdoc sets default var as no-default-arg - it has no default vars
+                        if (defaultvar != null && !defaultvar.equals("no-default-arg"))
+                        {
+                            // don't increment i, let ARGS pick it up.
+                            var = defaultvar;
+                        }
+                        else
+                        {
+                            throw new ConfigurationException.UnexpectedDefaults(null, null, -1);
+                        }
+                    }
+                    break;
+                }
+                case ARGS:
+                {
+                    if (varArgCount == -2)
+                    {
+                        if (isBoolean(buffer, var))
+                        {
+                            varArgCount = 0;
+                            mode = EXEC;
+                            break;
+                        }
+                        else
+                        {
+                            varArgCount = buffer.getVarArgCount(var);
+                        }
+                    }
+                    assert varArgCount >= -1; // just in case the getVarArgCount author was insane.
+
+                    if (args.length == i)
+                    {
+                        mode = EXEC;
+                        break;
+                    }
+
+                    boolean greedy = buffer.getInfo(var).isGreedy();
+
+                    // accumulating non-command arguments...
+
+                    // check for a terminator on our accumulated parameter list
+                    if (!greedy && dash && isAnArgument(args[i]))
+                    {
+                        if (varArgCount == -1)
+                        {
+                            // we were accumulating an unlimited set of args, a new var terminates that.
+                            mode = EXEC;
+                            break;
+                        }
+                        throw new ConfigurationException.IncorrectArgumentCount(varArgCount, argList.size(), var, source, -1);
+                    }
+
+                    argList.add(args[i++]);
+                    if (argList.size() == varArgCount)
+                    {
+                        mode = EXEC;
+                    }
+
+                    break;
+                }
+                case EXEC:
+                {
+                    if ((varArgCount != -1) && (argList.size() != varArgCount))
+                    {
+                        throw new ConfigurationException.IncorrectArgumentCount(varArgCount, argList.size(), var, source, -1);
+                    }
+                    if (varArgCount == 0) // boolean flag fakery...
+                        argList.add("true");
+
+                    if (vars.contains(var))
+                    {
+                        if ((defaultvar != null) && var.equals(defaultvar))
+                        {
+                            // we could perhaps accumulate the defaults spread out through
+                            // the rest of the flags, but for now we'll call this illegal.
+                            throw new ConfigurationException.InterspersedDefaults(var, source, -1);
+                        }
+                    }
+                    iEnd = i;
+                    buffer.setVar(var, new LinkedList<String>(argList), source, -1, null, append);
+                    buffer.addPosition(var, iStart, iEnd);
+                    append = false;
+                    vars.add(var);
+                    argList.clear();
+                    mode = START;
+                    break;
+                }
+                case DONE:
+                {
+                    assert false;
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Given a string like "-foo" or "-5" or "-123.mxml", this determines
+     * whether the string is an argument or... not an argument (e.g. numeral)
+     */
+    private static boolean isAnArgument(final String arg)
+    {
+        return (arg.startsWith("-") &&
+                // if the first character after a dash is numeric, this is not
+                // an argument, it is a parameter (and therefore non-terminating)
+                (arg.length() > 1) && !Character.isDigit(arg.charAt(1)));
+    }
+
+    private static Map<String, String> getAliases(ConfigurationBuffer buffer)
+    {
+        Map<String, String> aliases = new HashMap<String, String>();
+        aliases.putAll(buffer.getAliases());
+        for (final String varname : buffer.getVars())
+        {
+            if (varname.indexOf('.') == -1)
+                continue;
+
+            String leafname = varname.substring(varname.lastIndexOf('.') + 1);
+            if (aliases.containsKey(leafname))
+                continue;
+            aliases.put(leafname, varname);
+        }
+
+        return aliases;
+    }
+
+    private static boolean isBoolean(ConfigurationBuffer buffer, String var)
+    {
+        ConfigurationInfo info = buffer.getInfo(var);
+
+        if (info.getArgCount() > 1)
+            return false;
+
+        Class<?> c = info.getArgType(0);
+
+        return ((c == boolean.class) || (c == Boolean.class));
+    }
+
+    public static String brief(String program, String defaultvar, LocalizationManager l10n, String l10nPrefix)
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("defaultVar", defaultvar);
+        params.put("program", program);
+        return l10n.getLocalizedTextString(l10nPrefix + ".Brief", params);
+    }
+
+    public static String usage(String program, String defaultVar, ConfigurationBuffer cfgbuf, Set<String> keywords, LocalizationManager lmgr, String l10nPrefix)
+    {
+        boolean isCompc = program.contains("compc");
+        Map<String, String> aliases = getAliases(cfgbuf);
+        Map<String, String> sesaila = new HashMap<String, String>();
+        for (Iterator<Map.Entry<String, String>> it = aliases.entrySet().iterator(); it.hasNext();)
+        {
+            Map.Entry<String, String> e = it.next();
+            sesaila.put(e.getValue(), e.getKey());
+        }
+
+        TreeSet<String> printSet = new TreeSet<String>();
+
+        boolean all = false;
+        boolean advanced = false;
+        boolean details = false;
+        boolean syntax = false;
+        boolean printaliases = false;
+
+        // figure out behavior..
+        Set<String> newSet = new HashSet<String>();
+        for (Iterator<String> kit = keywords.iterator(); kit.hasNext();)
+        {
+            String keyword = kit.next();
+
+            if (keyword.equals("list"))
+            {
+                all = true;
+                newSet.add("*");
+            }
+            else if (keyword.equals("advanced"))
+            {
+                advanced = true;
+                if (keywords.size() == 1)
+                {
+                    all = true;
+                    newSet.add("*");
+                }
+            }
+            else if (keyword.equals("details"))
+            {
+                details = true;
+            }
+            else if (keyword.equals("syntax"))
+            {
+                syntax = true;
+            }
+            else if (keyword.equals("aliases"))
+            {
+                printaliases = true;
+            }
+            else
+            {
+                details = true;
+                newSet.add(keyword);
+            }
+        }
+        if (syntax)
+        {
+            final List<String> lines = ConfigurationBuffer.formatText(getSyntaxDescription(program, defaultVar, advanced, lmgr, l10nPrefix), 78);
+            return Joiner.on("\n").join(lines);
+        }
+        keywords = newSet;
+
+        // accumulate set to print
+        for (Iterator<String> kit = keywords.iterator(); kit.hasNext();)
+        {
+            String keyword = kit.next().toLowerCase();
+
+            for (final String var : cfgbuf.getVars())
+            {
+                ConfigurationInfo info = cfgbuf.getInfo(var);
+                
+                // If the client is not "compc", skip "compc-only" options.
+                if (info.isCompcOnly && !isCompc)
+                    continue;
+                
+                String description = getDescription(cfgbuf, var, lmgr, l10nPrefix);
+
+                if ((all
+                        || (var.indexOf(keyword) != -1)
+                        || ((description != null) && (description.toLowerCase().indexOf(keyword) != -1))
+                        || (keyword.matches(var))
+                        || ((sesaila.get(var) != null) && (sesaila.get(var)).indexOf(keyword) != -1))
+                     && (!info.isHidden())
+                     && (!info.isRemoved())
+                     && (advanced || !info.isAdvanced()))
+                {
+                    if (printaliases && sesaila.containsKey(var))
+                        printSet.add(sesaila.get(var));
+                    else
+                        printSet.add(var);
+                }
+                else
+                {
+                    /*
+                     * for (int i = 0; i < info.getAliases().length; ++i) {
+                     * String alias = info.getAliases()[i]; if (alias.indexOf(
+                     * keyword ) != -1) { printSet.add( var ); } }
+                     */
+                }
+            }
+        }
+
+        StringBuilder output = new StringBuilder(1024);
+
+        if (printSet.size() == 0)
+        {
+            String nkm = lmgr.getLocalizedTextString(l10nPrefix + ".NoKeywordsMatched");
+            output.append(nkm);
+            output.append("\n");
+        }
+        else
+            for (Iterator<String> it = printSet.iterator(); it.hasNext();)
+            {
+                String avar = it.next();
+                String var = avar;
+                if (aliases.containsKey(avar))
+                    var = aliases.get(avar);
+
+                ConfigurationInfo info = cfgbuf.getInfo(var);
+                assert info != null;
+
+                output.append("-");
+                output.append(avar);
+
+                int count = cfgbuf.getVarArgCount(var);
+                if ((count >= 1) && (!isBoolean(cfgbuf, var)))
+                {
+                    for (int i = 0; i < count; ++i)
+                    {
+                        output.append(" <");
+                        output.append(cfgbuf.getVarArgName(var, i));
+                        output.append(">");
+                    }
+                }
+                else if (count == -1)
+                {
+                    String last = "";
+                    for (int i = 0; i < 5; ++i)
+                    {
+                        String argname = cfgbuf.getVarArgName(var, i);
+                        if (!argname.equals(last))
+                        {
+                            output.append(" [");
+                            output.append(argname);
+                            output.append("]");
+                            last = argname;
+                        }
+                        else
+                        {
+                            output.append(" [...]");
+                            break;
+                        }
+                    }
+                }
+
+                output.append("\n");
+
+                if (details)
+                {
+                    StringBuilder description = new StringBuilder(160);
+                    if (printaliases)
+                    {
+                        if (aliases.containsKey(avar))
+                        {
+                            String fullname = lmgr.getLocalizedTextString(l10nPrefix + ".FullName");
+                            description.append(fullname);
+                            description.append(" -");
+                            description.append(aliases.get(avar));
+                            description.append("\n");
+                        }
+                    }
+                    else if (sesaila.containsKey(var))
+                    {
+                        String alias = lmgr.getLocalizedTextString(l10nPrefix + ".Alias");
+                        description.append(alias);
+                        description.append(" -");
+                        description.append(sesaila.get(var));
+                        description.append("\n");
+                    }
+
+                    String d = getDescription(cfgbuf, var, lmgr, l10nPrefix);
+                    if (var.equals("help") && (printSet.size() > 2))
+                    {
+                        String helpKeywords = lmgr.getLocalizedTextString(l10nPrefix + ".HelpKeywords");
+                        description.append(helpKeywords);
+                    }
+                    else if (d != null)
+                        description.append(d);
+
+                    String flags = "";
+                    if (info.isAdvanced())
+                    {
+                        String advancedString = lmgr.getLocalizedTextString(l10nPrefix + ".Advanced");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + advancedString);
+                    }
+                    if (info.allowMultiple())
+                    {
+                        String repeatableString = lmgr.getLocalizedTextString(l10nPrefix + ".Repeatable");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + repeatableString);
+                    }
+                    if ((defaultVar != null) && var.equals(defaultVar))
+                    {
+                        String defaultString = lmgr.getLocalizedTextString(l10nPrefix + ".Default");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + defaultString);
+                    }
+                    if (info.isRoyaleOnly())
+                    {
+                        String royaleOnlylString = Messages.getString("RoyaleOnly");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + royaleOnlylString);
+                    }
+                    if (flags.length() != 0)
+                    {
+                        flags += ")";
+                    }
+                    description.append(flags);
+
+                    List<String> descriptionLines = ConfigurationBuffer.formatText(description.toString(), 70);
+
+                    for (final String next : descriptionLines)
+                    {
+                        output.append("    ");
+                        output.append(next);
+                        output.append("\n");
+                    }
+                }
+            }
+        return output.toString();
+    }
+
+    public static String getDescription(ConfigurationBuffer buffer, String var, LocalizationManager l10n, String l10nPrefix)
+    {
+        String key = (l10nPrefix == null) ? var : (l10nPrefix + "." + var);
+        String description = l10n.getLocalizedTextString(key, null);
+
+        return description;
+    }
+
+    public static String getSyntaxDescription(String program, String defaultVar, boolean advanced, LocalizationManager l10n, String l10nPrefix)
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("defaultVar", defaultVar);
+        params.put("program", program);
+
+        String key = l10nPrefix + "." + (advanced ? "AdvancedSyntax" : "Syntax");
+        String text = l10n.getLocalizedTextString(key, params);
+
+        if (text == null)
+        {
+            text = "No syntax help available, try '-help list' to list available configuration variables.";
+            assert false : "Localized text for syntax description not found!";
+        }
+        return text;
+    }
+
+    public static final String source = SOURCE_COMMAND_LINE;
+}
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
new file mode 100644
index 0000000..3f33aec
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/Configuration.java
@@ -0,0 +1,204 @@
+/*
+ *
+ *  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.config;
+
+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.exceptions.ConfigurationException;
+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;
+import org.apache.royale.compiler.internal.config.annotations.Mapping;
+import org.apache.royale.compiler.problems.DeprecatedConfigurationOptionProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.problems.RemovedConfigurationOptionProblem;
+
+public class Configuration {
+
+    private static Map<String, String> aliases = null;
+
+    public static Map<String, String> getAliases()
+    {
+        if (aliases == null)
+        {
+            aliases = new HashMap<String, String>();
+
+            aliases.put("w", "write-files");
+            aliases.put("l", "list-files");
+        }
+        return aliases;
+    }
+	
+    /**
+     * Collection of fatal and non-fatal configuration problems.
+     */
+    private Collection<ICompilerProblem> configurationProblems = new ArrayList<ICompilerProblem>();
+
+    /**
+     * Get the configuration problems. This should be called after the configuration has been processed.
+     * 
+     * @return a collection of fatal and non-fatal configuration problems.
+     */
+    public Collection<ICompilerProblem> getConfigurationProblems()
+    {
+        return configurationProblems;
+    }
+
+    /**
+     * Validate configuration options values.
+     * 
+     * @param configurationBuffer Configuration buffer.
+     * @throws ConfigurationException Error.
+     */
+    public void validate(ConfigurationBuffer configurationBuffer) throws ConfigurationException
+    {
+        // process the merged configuration buffer. right, can't just process the args.
+        processDeprecatedAndRemovedOptions(configurationBuffer);
+    }
+
+    private void processDeprecatedAndRemovedOptions(ConfigurationBuffer configurationBuffer)
+    {
+        for (final String var : configurationBuffer.getVars())
+        {
+            ConfigurationInfo info = configurationBuffer.getInfo(var);
+            List<ConfigurationValue> values = configurationBuffer.getVar(var);
+            if (values != null)
+            {
+                for (final ConfigurationValue cv : values)
+                {
+                    if (info.isRemoved())
+                    {
+                        addRemovedConfigurationOptionProblem(cv);
+                    }
+                    else if (info.isDeprecated() && configurationBuffer.getVar(var) != null)
+                    {
+                        String replacement = info.getDeprecatedReplacement();
+                        String since = info.getDeprecatedSince();
+                        DeprecatedConfigurationOptionProblem problem = new DeprecatedConfigurationOptionProblem(var,
+                                replacement, since, cv.getSource(), cv.getLine());
+                        configurationProblems.add(problem);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Add a RemovedConfigurationOptionProblem to the list of configuration problems.
+     * 
+     * @param cv
+     */
+    private void addRemovedConfigurationOptionProblem(ConfigurationValue cv)
+    {
+        RemovedConfigurationOptionProblem problem = new RemovedConfigurationOptionProblem(cv.getVar(), cv.getSource(),
+                cv.getLine());
+        configurationProblems.add(problem);
+    }
+
+    //
+    // 'help' option from CommandLineConfiguration
+    //
+
+    /**
+     * dummy, just a trigger for help text
+     */
+    @Config(displayed = false, greedy = true)
+    @Arguments("keyword")
+    @InfiniteArguments
+    public void setHelp(ConfigurationValue cv, String[] keywords)
+    {
+
+    }
+
+    //
+    // 'version' option from CommandLineConfiguration
+    //
+
+    /**
+     * Dummy option. Just a trigger for version info.
+     */
+    @Config
+    public void setVersion(ConfigurationValue cv, boolean value)
+    {
+    }
+
+    //
+    // 'files' option
+    //
+
+    private List<String> files = new ArrayList<String>();
+
+    /**
+     * @return A list of filespecs. It's the default variable for command line.
+     */
+    public List<String> getFiles()
+    {
+        return files;
+    }
+
+    @Config(allowMultiple = true, hidden = true)
+    @Mapping("files")
+    @Arguments(Arguments.PATH_ELEMENT)
+    @InfiniteArguments
+    public void setFiles(ConfigurationValue cv, List<String> args) throws ConfigurationException
+    {
+        this.files.addAll(args);
+    }
+
+    //
+    // 'write-files' option
+    //
+
+    private boolean writeFiles = false;
+
+    public boolean getWriteFiles()
+    {
+        return writeFiles;
+    }
+
+    @Config
+    @Mapping("write-files")
+    public void setWriteFiles(ConfigurationValue cv, boolean b)
+    {
+        this.writeFiles = b;
+    }
+
+    //
+    // 'list-files' option
+    //
+
+    private boolean listFiles = false;
+
+    public boolean getListFiles()
+    {
+        return listFiles;
+    }
+
+    @Config
+    @Mapping("list-files")
+    public void setListFiles(ConfigurationValue cv, boolean b)
+    {
+        this.listFiles = b;
+    }
+}
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationBuffer.java b/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationBuffer.java
new file mode 100644
index 0000000..35a6e37
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationBuffer.java
@@ -0,0 +1,1346 @@
+/*
+ *
+ *  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.config;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.internal.config.IConfigurationFilter;
+import org.apache.royale.compiler.internal.config.annotations.ArgumentNameGenerator;
+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.RoyaleOnly;
+import org.apache.royale.compiler.internal.config.annotations.InfiniteArguments;
+import org.apache.royale.compiler.internal.config.annotations.Mapping;
+import org.apache.royale.compiler.internal.config.annotations.SoftPrerequisites;
+import org.apache.royale.compiler.problems.ConfigurationProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.utils.Trace;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * 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>
+ */
+public final class ConfigurationBuffer
+{
+    public ConfigurationBuffer(Class<? extends Configuration> configClass)
+    {
+        this(configClass, new HashMap<String, String>());
+    }
+
+    public ConfigurationBuffer(Class<? extends Configuration> 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<? extends Configuration> configClass, Map<String, String> aliases, IConfigurationFilter filter)
+    {
+        this.configClass = configClass;
+        this.varMap = new HashMap<String, List<ConfigurationValue>>();
+        this.committed = new HashSet<String>();
+
+        loadCache(configClass, filter);
+        assert (varCache.size() > 0) : "coding error: nothing was configurable in the provided object!";
+        for (Map.Entry<String, String> e : aliases.entrySet())
+        {
+            addAlias(e.getKey(), e.getValue());
+        }
+    }
+
+    public ConfigurationBuffer(ConfigurationBuffer copyFrom, boolean copyCommitted)
+    {
+        this.configClass = copyFrom.configClass;
+        this.varMap = new HashMap<String, List<ConfigurationValue>>(copyFrom.varMap);
+        this.committed = copyCommitted ? new HashSet<String>(copyFrom.committed) : new HashSet<String>();
+        this.varCache = copyFrom.varCache; // doesn't change after creation
+        this.varList = copyFrom.varList; // doesn't change after creation
+        this.tokens = new HashMap<String, String>(copyFrom.tokens);
+    }
+
+    public final List<String> dump()
+    {
+        final List<String> dump = new ArrayList<String>(varCache.size());
+        for (final Map.Entry<String, ConfigurationInfo> entry : varCache.entrySet())
+        {
+            dump.add(entry.getKey() + "," + entry.getValue().toString());
+        }
+        Collections.sort(dump);
+        return dump;
+    }
+
+    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)
+        {
+            addAnyDefaultArgValues(var, argCount, vals);
+
+            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);
+    }
+
+    /**
+     * Remove the configuration values came from the given source.
+     * 
+     * @param source source name
+     * @see CommandLineConfigurator#SOURCE_COMMAND_LINE
+     */
+    public void clearSourceVars(String source)
+    {
+        List<String> remove = new LinkedList<String>();
+        for (Map.Entry<String, List<ConfigurationValue>> e : varMap.entrySet())
+        {
+            String var = e.getKey();
+            List<ConfigurationValue> vals = e.getValue();
+
+            List<ConfigurationValue> newvals = new LinkedList<ConfigurationValue>();
+            for (ConfigurationValue val : vals)
+            {
+                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 (value == null)
+
+                    {
+                        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;
+            }
+        }
+    }
+
+    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<ConfigurationValue> getVar(String avar)
+    {
+        String var = unalias(avar);
+        return varMap.get(var);
+    }
+
+    public Set<String> getVars()
+    {
+        return varCache.keySet();
+    }
+
+    public void merge(ConfigurationBuffer other)
+    {
+        assert (configClass == other.configClass);
+        varMap.putAll(other.varMap);
+        committed.addAll(other.committed);
+    }
+
+    private final Map<String, List<ConfigurationValue>> varMap; // list of vars that have been set
+    private final Set<String> committed; // set of vars committed to backing config
+    private final Class<? extends Configuration> 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, 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 INFO_SUFFIX = "Info";
+
+    //-----------------------------------------------
+    //
+
+    /**
+     * WORKAROUND FOR BUG CMP-396
+     * 
+     * <p>
+     * {@link #c2h(String)} generates option names based on cfgXXX names in
+     * {@code Configuration}. Since we collapsed all the sub-configurations into
+     * one class, there's no longer a "base name" like "compiler.*" or
+     * "compiler.fonts.*". In order to preserve the dotted naming convention, we
+     * need to know which "-" separated names are actually dotted names. The
+     * {@link #CONVERT_FROM} and {@link #CONVERT_TO} is an <b>ordered</b> lookup
+     * table for option group base names. It's order makes sure that the longest
+     * possible replacement is done.
+     */
+    private static final ImmutableList<String> CONVERT_FROM =
+            ImmutableList.of(
+                    "compiler-fonts-languages-",
+                    "compiler-fonts-",
+                    "compiler-namespaces-",
+                    "compiler-mxml-",
+                    "compiler-",
+                    "metadata-",
+                    "licenses-",
+                    "frames-",
+                    "runtime-shared-library-settings-");
+
+    private static final ImmutableList<String> CONVERT_TO =
+            ImmutableList.of(
+                    "compiler.fonts.languages.",
+                    "compiler.fonts.",
+                    "compiler.namespaces.",
+                    "compiler.mxml.",
+                    "compiler.",
+                    "metadata.",
+                    "licenses.",
+                    "frames.",
+                    "runtime-shared-library-settings.");
+
+    /**
+     * 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));
+            }
+        }
+        final String combined = b.toString();
+
+        for (int i = 0; i < CONVERT_FROM.size(); i++)
+        {
+            if (combined.startsWith(CONVERT_FROM.get(i)))
+            {
+                return combined.replaceFirst(CONVERT_FROM.get(i), CONVERT_TO.get(i));
+            }
+        }
+        return combined;
+    }
+
+    /**
+     * 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();
+    }
+
+    public static String varname(String membername, String basename)
+    {
+        return ((basename == null) ? membername : (basename + "." + membername));
+    }
+
+    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());
+        @SuppressWarnings("unchecked")
+        Class<? extends Configuration> cfgClass = (Class<? extends Configuration>)setterMethod.getDeclaringClass();
+
+        Method infoMethod = null, getterMethod = null;
+        if (!setterMethod.isAnnotationPresent(Config.class))
+        {
+            try
+            {
+                infoMethod = cfgClass.getMethod(infoMethodName);
+    
+                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);
+    
+            }
+            catch (SecurityException e)
+            {
+                e.printStackTrace();
+            }
+            catch (NoSuchMethodException e)
+            {
+            }
+            catch (IllegalArgumentException e)
+            {
+                e.printStackTrace();
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+            catch (InvocationTargetException e)
+            {
+                e.printStackTrace();
+            }
+        }
+        
+        if (info == null)
+            info = new ConfigurationInfo();
+
+        try
+        {
+            getterMethod = cfgClass.getMethod(getterMethodName, (Class[])null);
+        }
+        catch (SecurityException e)
+        {
+            e.printStackTrace();
+        }
+        catch (NoSuchMethodException e)
+        {
+        }
+        info.setSetterMethod(setterMethod);
+        info.setGetterMethod(getterMethod);
+
+        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<? extends Configuration> cfg, IConfigurationFilter filter)
+    {
+        int count = 0;
+
+        // First, find all vars at this level.
+        for (final Method method : cfg.getMethods())
+        {
+            if (method.getName().startsWith(SET_PREFIX) ||
+                method.isAnnotationPresent(Config.class))
+            {
+                String configName = null;
+
+                final Class<?>[] pt = method.getParameterTypes();
+                assert pt.length > 1 : "Expected at least one parameters on setter.";
+
+                // Collect configuration info from getXXXInfo() static methods.
+                final ConfigurationInfo info = createInfo(method);
+
+                // Collect configuration info from annotations.
+                final Config config = method.getAnnotation(Config.class);
+                if (config != null)
+                {
+                    info.isAdvanced = config.advanced();
+                    info.isHidden = config.hidden();
+                    info.isRemoved = config.removed();
+                    info.allowMultiple = config.allowMultiple();
+                    info.isPath = config.isPath();
+                    info.isDisplayed = config.displayed();
+                    info.isCompcOnly = config.compcOnly();
+                    info.isRequired = config.isRequired();
+                    
+                    // Argument name generator class
+                    final ArgumentNameGenerator argumentNameGeneratorClass = 
+                        method.getAnnotation(ArgumentNameGenerator.class);
+                    if (argumentNameGeneratorClass != null)
+                    {
+                        info.argNameGeneratorClass = argumentNameGeneratorClass.value();
+                    }
+                    else
+                    {
+                        // Argument names
+                        final Arguments arguments = method.getAnnotation(Arguments.class);
+                        if (arguments != null)
+                            info.argnames = arguments.value();
+                    }
+                    
+                    // Argument count
+                    final InfiniteArguments infinite = method.getAnnotation(InfiniteArguments.class);
+                    if (infinite != null)
+                        info.argcount = ConfigurationInfo.INFINITE_ARGS;
+
+                    // Soft Prerequisites
+                    final SoftPrerequisites softPre = method.getAnnotation(SoftPrerequisites.class);
+                    if (softPre != null)
+                        info.softPrerequisites = softPre.value();
+                    
+                    // XML element name for configuration
+                    final Mapping mapping = method.getAnnotation(Mapping.class);
+                    if (mapping != null)
+                        configName = Joiner.on(".").skipNulls().join(mapping.value());
+                    
+                    // Is this a Flex only option?
+                    final RoyaleOnly royaleOnly = method.getAnnotation(RoyaleOnly.class);
+                    if (royaleOnly != null)
+                        info.isRoyaleOnly = true;
+                }
+
+                // Fall back to naming convention for configuration names.
+                if (configName == null)
+                    configName = c2h(method.getName().substring(SET_PREFIX.length()));
+
+                if( filter == null || filter.select(configName) )
+                {
+                    varCache.put(configName, info);
+                    varList.add(configName);
+                    if (info.isRequired())
+                    {
+                        requiredList.add(configName);
+                    }
+                    ++count;
+                }
+            }
+        }
+
+        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);
+    }
+
+    public String getVarArgName(String avar, int argnum)
+    {
+        String var = unalias(avar);
+        ConfigurationInfo info = getInfo(var);
+
+        if (info == null)
+        {
+            assert false : ("must call isValid to check vars!");
+        }
+
+        return info.getArgName(argnum);
+    }
+
+    public boolean isValidVar(String avar)
+    {
+        String var = unalias(avar);
+        ConfigurationInfo info = getInfo(var);
+        return (info != null);
+    }
+
+    public int getVarArgCount(String avar)
+    {
+        ConfigurationInfo info = getInfo(avar);
+        assert (info != null);
+        return info.getArgCount();
+    }
+
+    /**
+     * Add any default values to an argument, if the user did not specify them
+     * on the command line.
+     * 
+     * @param avar the argument variable
+     * @param argCount the number of argument values specified
+     * @param vals Values to add any default values to
+     */
+    private void addAnyDefaultArgValues(String avar, int argCount, List<String> vals)
+    {
+        ConfigurationInfo info = getInfo(avar);
+        final int missingArgsCount = argCount - vals.size();
+        if (missingArgsCount == 0 || info.getDefaultArgValues() == null)
+            return;
+
+        final String[] defaultArgValues = info.getDefaultArgValues();
+        final int defaultArgsCount = defaultArgValues.length;
+        final int defaultArgsStart = defaultArgsCount - missingArgsCount;
+        for (int i = defaultArgsStart; i < defaultArgsCount; i++)
+        {
+            vals.add(defaultArgValues[i]);
+        }
+    }
+
+    /**
+     * commit - bake the resolved map to the configuration
+     * 
+     * @param config The configuration to set the buffer variables into.
+     * @param problems A collection where configuration problems are reported.
+     * 
+     * @return true if successful, false otherwise.
+     */
+    public boolean commit(Object config, Collection<ICompilerProblem> problems)
+    {
+        assert (config.getClass() == configClass) : 
+            ("coding error: configuration " + config.getClass() + " != template " + configClass);
+        Set<String> done = new HashSet<String>();
+        boolean success = true;
+        
+        for (Iterator<String> vars = varList.iterator(); vars.hasNext();)
+        {
+            String var = vars.next();
+            if (varMap.containsKey(var))
+            {
+                try
+                {
+                    commitVariable(config, var, done);
+                }
+                catch (ConfigurationException e)
+                {
+                    problems.add(new ConfigurationProblem(e));
+                    success = false;
+                }
+            }
+        }
+
+        for (Iterator<String> reqs = requiredList.iterator(); reqs.hasNext();)
+        {
+            String req = reqs.next();
+
+            if (!committed.contains(req))
+            {
+                ConfigurationException e = new ConfigurationException.MissingRequirement(req, null, null, -1);
+                problems.add(new ConfigurationProblem(
+                        null,
+                        -1,
+                        -1,
+                        -1,
+                        -1,
+                        e.getMessage()));
+                success = false;
+            }
+        }
+        
+        return success;
+    }
+
+    /**
+     * 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<ConfigurationValue> vals = varMap.get(var);
+
+        if (vals.size() > 1)
+        {
+            assert (info.allowMultiple()); // assumed to have been previously checked
+        }
+        for (ConfigurationValue val : vals)
+        {
+            try
+            {
+                Object[] args = buildArgList(info, val);
+
+                info.getSetterMethod().invoke(config, 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 );
+
+                    String newDepvar = car + "." + depvar;
+
+                    // Since in royale we have collapsed sub-configurations into one
+                    // configuration, some options that were in sub-configurations now
+                    // have prerequisites on options in the same configuration. We
+                    // need to keep the old configuration mappings so old configurations
+                    // options will still work. So a simple thing we can do is if the 
+                    // dependency variable is invalid (presumably because the 
+                    // dependency is really on a parent configuration option), 
+                    // then use the dependency as is depvar) and see if it is 
+                    // valid. If depvar ends up not being valid then set depvar
+                    // to newDepvar so error reporting isn't changed by the new
+                    // fall-back behavior.
+                    if (isValidVar(newDepvar) || !isValidVar(depvar))
+                        depvar = newDepvar;
+
+                }
+
+                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))
+                    {
+                        // TODO - can we get source/line for this?
+                        throw new ConfigurationException.MissingRequirement(depvar, var, null, -1);
+                    }
+                }
+            }
+        }
+    }
+
+    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<String> 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<ConfigurationValue> 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<ConfigurationValue> srcList = getVar(avar);
+        if (srcList == null)
+            return null;
+
+        List<ConfigurationValue> dstList = new LinkedList<ConfigurationValue>();
+        for (ConfigurationValue srcVal : srcList)
+        {
+            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;
+    }
+
+    public static 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;
+    }
+
+    /**
+     * For debugging only.
+     * <p>
+     * Produces an alphabetized list of this buffer's configuration options and their values.
+     * An option such as
+     * <pre>
+     * -foo=aaa,bbb -foo+=ccc
+     * </pre>
+     * will appear as
+     * <pre>
+     * foo=aaa,bbb;ccc
+     * </pre>
+     */
+    @Override
+    public String toString()
+    {
+        StringBuffer sb = new StringBuffer();
+        
+        String[] variables = varMap.keySet().toArray(new String[0]);
+        Arrays.sort(variables);
+        
+        for (String var : variables)
+        {
+            sb.append(var);
+            sb.append("=");
+
+            ArrayList<String> commaSeparatedValues = new ArrayList<String>();
+            for (ConfigurationValue cv : varMap.get(var))
+            {
+                List<String> args = cv.getArgs();
+                String joinedArgs = Joiner.on(',').join(args);
+                commaSeparatedValues.add(joinedArgs);
+            }
+            String rhs = Joiner.on(';').join(commaSeparatedValues);
+            sb.append(rhs);
+            
+            sb.append('\n');
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationInfo.java b/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationInfo.java
new file mode 100644
index 0000000..53836a7
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationInfo.java
@@ -0,0 +1,473 @@
+/*
+ *
+ *  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.config;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.apache.royale.compiler.internal.config.annotations.DefaultArgumentValue;
+import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
+
+/**
+ * Meta information for each configuration options. It is created by
+ * {@link ConfigurationBuffer#loadCache} from either annotations or
+ * {@code public static ConfigurationInfo getFooInfo();} methods in
+ * {@link Configuration} class.
+ */
+public class ConfigurationInfo
+{
+    public static final int NOT_SET = -2;
+    public static final int INFINITE_ARGS = -1;
+
+    /**
+     * This ctor is used when everything can be introspected off the setter
+     * method, or else when the names/types are provided by method overrides
+     * rather than ctor arguments
+     */
+    public ConfigurationInfo()
+    {
+        this.argcount = NOT_SET;
+        this.argnames = null;
+    }
+
+    /**
+     * Simple ctor for restricting the number of arguments.
+     * 
+     * @param argcount number of args, -1 for an infinite list
+     */
+    public ConfigurationInfo(int argcount)
+    {
+        this.argcount = argcount;
+        this.argnames = null;
+    }
+
+    /**
+     * Simple ctor for naming the arguments.
+     * 
+     * @param argnames list of argnames, argcount will default to # of elements
+     */
+    public ConfigurationInfo(String argnames[])
+    {
+        this.argcount = argnames.length;
+        this.argnames = argnames;
+    }
+
+    /**
+     * Use this ctor when you want to set a single list of some number of
+     * identically named args
+     * 
+     * @param argcount number of arguments (-1 for infinite)
+     * @param argname name of each argument
+     */
+    public ConfigurationInfo(int argcount, String argname)
+    {
+        this.argcount = argcount;
+        this.argnames = new String[] {argname};
+    }
+
+    /**
+     * More unusual ctor, this would let you have the first few args named one
+     * thing, the rest named something else. It is far more likely that you want
+     * a constrained list of names or else an arbitrary list of identical names.
+     * 
+     * @param argcount number of arguments
+     * @param argnames array of argument names
+     */
+    public ConfigurationInfo(int argcount, String argnames[])
+    {
+        this.argcount = argcount;
+        this.argnames = argnames;
+    }
+
+    public final int getArgCount()
+    {
+        return argcount;
+    }
+
+    protected int argcount = NOT_SET;
+
+    protected String[] defaultArgValues = null;
+
+    /**
+     * Get any default values for an argument
+     * 
+     * @return an array of default argument values.  May be null
+     */
+    public final String[] getDefaultArgValues()
+    {
+        return defaultArgValues;
+    }
+
+    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();
+    }
+
+    /**
+     * Return the name of each parameter. The default implementation is usually
+     * sufficient for simple cases, but one could do wacky things here like
+     * support an infinite list of alternating arg names.
+     * 
+     * @param argnum The argument number.
+     * @return name of argument
+     */
+    public String getArgName(int argnum)
+    {
+        if (argNameGeneratorClass != null)
+        {
+            Method getArgNameMethod;
+            try
+            {
+                getArgNameMethod = argNameGeneratorClass.getMethod("getArgumentName", int.class);
+                return (String)getArgNameMethod.invoke(null, argnum);
+            }
+            catch (Exception e)
+            {
+                // TODO: connect these exception to our problem logging subsystem.
+                e.printStackTrace();
+            }
+
+            return "";
+        }
+
+        if ((argnames == null) || (argnames.length == 0))
+        {
+            return classToArgName(getArgType(argnum));
+        }
+        else if (argnum >= argnames.length)
+        {
+            return argnames[argnames.length - 1];
+        }
+        else
+        {
+            return argnames[argnum];
+        }
+    }
+
+    /**
+     * Return the type of each parameter. This is computed based on your setter,
+     * and cannot be overridden
+     * 
+     * @param argnum The argument number.
+     */
+    public final Class<?> getArgType(int argnum)
+    {
+        if (argnum >= argtypes.length)
+        {
+            return argtypes[argtypes.length - 1];
+        }
+        else
+        {
+            return argtypes[argnum];
+        }
+    }
+
+    protected Class<?> argNameGeneratorClass;
+    protected String[] argnames;
+    protected Class<?>[] argtypes;
+
+    protected String[] prerequisites = null;
+
+    /**
+     * Return variable names that should be set before this one. The buffer is
+     * always set such that it tries to set all variables at a given level
+     * before setting child values, but you could override by using this. Its
+     * probably a bad idea to depend on children, though. It is unnecessary to
+     * set parent vars as prerequisites, since they are implicitly set first
+     */
+    public String[] getPrerequisites()
+    {
+        return prerequisites;
+    }
+
+    protected String[] softPrerequisites = null;
+
+    /**
+     * Prerequisites which should be set before this one if they exist
+     */
+    public String[] getSoftPrerequisites()
+    {
+        return softPrerequisites;
+    }
+
+    protected boolean allowMultiple = false;
+
+    /**
+     * Variables are generally only allowed to be set once in a given
+     * file/cmdline. It is sometimes useful to allow the same set multiple times
+     * in order to aggregate values.
+     * 
+     * @return true if the setter can be called multiple times
+     */
+    public boolean allowMultiple()
+    {
+        return allowMultiple;
+    }
+
+    protected String[] aliases = null;
+
+    /**
+     * Return an array of other names for this variable.
+     */
+    public String[] getAliases()
+    {
+        return aliases;
+    }
+
+    protected boolean isAdvanced = false;
+
+    /**
+     * Override to make a variable hidden by default (i.e. you need -advanced on
+     * the cmdline)
+     */
+    public boolean isAdvanced()
+    {
+        return isAdvanced;
+    }
+
+    protected boolean isHidden = false;
+
+    /**
+     * Override to make a variable completely hidden
+     */
+    public boolean isHidden()
+    {
+        return isHidden;
+    }
+
+    protected boolean isDisplayed = true;
+
+    /**
+     * Override to prevent printing when dumping configuration
+     */
+    public boolean isDisplayed()
+    {
+        return isDisplayed;
+    }
+
+    /**
+     * If a variable -must- be set, override this
+     */
+    public boolean isRequired()
+    {
+        return isRequired;
+    }
+
+    protected boolean isRequired = false;
+
+    /**
+     * Magic used by the command line configurator only at the moment to decide
+     * whether this variable should eat all subsequent arguments. Useful for
+     * -help...
+     */
+    public boolean isGreedy()
+    {
+        return isGreedy;
+    }
+
+    protected boolean isGreedy = false;
+
+    public boolean isPath()
+    {
+        return isPath;
+    }
+
+    protected boolean isPath = false;
+
+    public boolean doChecksum()
+    {
+        return true;
+    }
+
+    public String getDeprecatedMessage()
+    {
+        return deprecatedMessage;
+    }
+
+    protected String deprecatedMessage = null;
+
+    public boolean isDeprecated()
+    {
+        return isDeprecated;
+    }
+
+    protected boolean isDeprecated = false;
+
+    public String getDeprecatedReplacement()
+    {
+        return deprecatedReplacement;
+    }
+
+    protected String deprecatedReplacement;
+
+    public String getDeprecatedSince()
+    {
+        return deprecatedSince;
+    }
+
+    protected String deprecatedSince;
+    
+    /**
+     * @return True indicates that the option is no longer 
+     * supported and will not have any affect.
+     */
+    public boolean isRemoved() 
+    {
+        return isRemoved;
+    }
+
+    protected boolean isRemoved = false;
+    
+    /**
+     * @return True the option requires Flex in order to be useful. 
+     */
+    public boolean isRoyaleOnly() 
+    {
+        return isRoyaleOnly;
+    }
+
+    protected boolean isRoyaleOnly = false;
+    
+    protected final void setSetterMethod(Method setter)
+    {
+        Class<?>[] pt = setter.getParameterTypes();
+
+        assert (pt.length >= 2) : ("coding error: config setter must take at least 2 args!");
+
+        this.setter = setter;
+
+        if (pt.length == 2)
+        {
+            Class<?> c = pt[1];
+
+            if (ConfigurationBuffer.isSupportedListType(c))
+            {
+                if (argcount == NOT_SET)
+                    argcount = -1; // infinite list
+
+                argtypes = new Class[] {String.class};
+                return;
+            }
+            else if (ConfigurationBuffer.isSupportedValueType(c))
+            {
+                assert (argcount == NOT_SET) : ("coding error: value object setter cannot override argcount");
+                assert (argnames == null) : ("coding error: value object setter cannot override argnames");
+
+                Field[] fields = c.getFields();
+
+                argcount = fields.length;
+
+                assert (argcount > 0) : ("coding error: " + setter + " value object " + c.getName() + " must contain at least one public field");
+
+                argnames = new String[fields.length];
+                argtypes = new Class[fields.length];
+
+                for (int f = 0; f < fields.length; ++f)
+                {
+                    argnames[f] = ConfigurationBuffer.c2h(fields[f].getName());
+                    argtypes[f] = fields[f].getType();
+                }
+                return;
+            }
+        }
+
+        assert ((argcount == NOT_SET) || (argcount == pt.length - 1)) : ("coding error: the argument count must match the number of setter arguments");
+        // We've taken care of lists and value objects, from here on out, it must match the parameter list.
+
+        argcount = pt.length - 1;
+
+        DefaultArgumentValue defaultArgValuesAnno = setter.getAnnotation(DefaultArgumentValue.class);
+        if (defaultArgValuesAnno != null)
+            defaultArgValues = defaultArgValuesAnno.value();
+
+        argtypes = new Class[pt.length - 1];
+        for (int i = 1; i < pt.length; ++i)
+        {
+            assert (ConfigurationBuffer.isSupportedSimpleType(pt[i])) : ("coding error: " + setter.getClass().getName() + "." + setter.getName() + " parameter " + i + " is not a supported type!");
+            argtypes[i - 1] = pt[i];
+        }
+    }
+
+    protected final Method getSetterMethod()
+    {
+        return setter;
+    }
+
+    private Method setter;
+    private Method getter;
+
+    protected final void setGetterMethod(Method getter)
+    {
+        this.getter = getter;
+    }
+
+    protected final Method getGetterMethod()
+    {
+        return getter;
+    }
+
+    @Override
+    public String toString()
+    {
+        return MoreObjects.toStringHelper("")
+                .add("alias", arrayAsString(getAliases()))
+                .add("argcount", getArgCount())
+                .add("argnames", arrayAsString(argnames))
+                .add("argtypes", arrayAsString(argtypes))
+                .add("deprecated", isDeprecated())
+                .add("deprecatedMessage", getDeprecatedMessage())
+                .add("deprecatedReplacement", getDeprecatedReplacement())
+                .add("deprecatedSince", getDeprecatedSince())
+                .add("getter", getGetterMethod() == null ? "null" : getGetterMethod().getName())
+                .add("setter", getSetterMethod() == null ? "null" : getSetterMethod().getName())
+                .add("required", isRequired())
+                .add("Prerequisites", arrayAsString(getPrerequisites()))
+                .add("softPrerequisites", arrayAsString(getSoftPrerequisites()))
+                .add("advanced", isAdvanced())
+                .add("allow multiple", allowMultiple())
+                //.add("doChecksum", doChecksum())
+                .add("displayed", isDisplayed())
+                .add("greedy", isGreedy())
+                .add("hidden", isHidden())
+                .add("removed", isRemoved())
+                .add("path", isPath())
+                .toString();
+    }
+
+    private String arrayAsString(Object[] array)
+    {
+        if (array == null)
+            return "";
+        else
+            return "[" + Joiner.on(",").join(array) + "]";
+    }
+
+    /**
+     * True if only {@code compc} client can use this option.
+     */
+    public boolean isCompcOnly = false;
+}
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationValue.java b/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationValue.java
new file mode 100644
index 0000000..a768560
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/ConfigurationValue.java
@@ -0,0 +1,109 @@
+/*
+ *
+ *  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.config;
+
+import java.util.List;
+import java.util.LinkedList;
+
+
+/**
+ * This class represents an instance of a configuration option.  For
+ * example, "-debug=true".
+ */
+public class ConfigurationValue
+{
+    protected ConfigurationValue( ConfigurationBuffer buffer, String var, List<String> args, String source, int line, String context )
+    {
+        this.buffer = buffer;
+        this.var = var;
+        this.args = new LinkedList<String>( args );
+        this.source = source;
+        this.line = line;
+        this.context = context;
+    }
+
+    /**
+     * getArgs
+     *
+     * @return list of values provided, in schema order
+     */
+    public final List<String> getArgs()
+    {
+        return args;
+    }
+
+    /**
+     * getBuffer
+     *
+     * @return a handle to the associated buffer holding this value
+     */
+    public final ConfigurationBuffer getBuffer()
+    {
+        return buffer;
+    }
+
+    /**
+     * getSource
+     *
+     * @return a string representing the origin of this value, or null if unknown
+     */
+    public final String getSource()
+    {
+        return source;
+    }
+
+    /**
+     * getLine
+     *
+     * @return the line number of the origin of this value, or -1 if unknown
+     */
+    public final int getLine()
+    {
+        return line;
+    }
+
+    /**
+     * getVar
+     *
+     * @return the full name of this configuration variable in the hierarchy
+     */
+    public final String getVar()
+    {
+        return var;
+    }
+
+    /**
+     * getContext
+     *
+     * @return the path of the enclosing context where the variable was set
+     * (i.e. the directory where the config file was found)
+     */
+    public final String getContext()
+    {
+        return context;
+    }
+
+    private final ConfigurationBuffer buffer;
+    private final String var;
+    private final List<String> args;
+    private final String source;
+    private final int line;
+    private final String context;
+}
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
new file mode 100644
index 0000000..c2abb48
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/Configurator.java
@@ -0,0 +1,683 @@
+/*
+ *
+ *  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.config;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+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.exceptions.ConfigurationException;
+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.utils.FilenameNormalization;
+import org.apache.royale.utils.Trace;
+
+/**
+ * A class that allows a client change compiler settings and to 
+ * configure projects and targets from those settings.
+ */
+public class Configurator implements Cloneable
+{
+    /**
+     * Convert file path strings to {@code File} objects. Null values are
+     * discarded.
+     * 
+     * @param paths List of file paths
+     * @return List of File objects. No null values will be returned.
+     */
+    public static List<File> toFiles(final List<String> paths)
+    {
+        final List<File> result = new ArrayList<File>();
+        for (final String path : paths)
+        {
+            if (path != null)
+                result.add(new File(path));
+        }
+        return result;
+    }
+
+    /**
+     * Convert file path strings to {@code File} objects. Null values are
+     * discarded.
+     * 
+     * @param paths List of file paths
+     * @return Array of File objects. No null values will be returned.
+     */
+    public static List<File> toFileList(final List<String> paths)
+    {
+        final List<File> result = new ArrayList<File>();
+        for (final String path : paths)
+        {
+            if (path != null)
+                result.add(FilenameNormalization.normalize(new File(path)));
+        }
+        return result;
+    }
+
+    /**
+     * Convert {@code File} objects to {@code String}, where each {@code String} is 
+     * the absolute file path of the file. Null values are discarded.
+     * 
+     * @param files file specifications
+     * @return Array of File objects. No null values will be returned.
+     */
+    public static String[] toPaths(File[] files)
+    {
+        final List<String> result = new ArrayList<String>();
+        for (final File file : files)
+        {
+            if (file != null)
+                result.add(file.getAbsolutePath());
+        }
+        return result.toArray(new String[0]);
+    }
+
+    // Used to generate the command line
+    private static final String EQUALS_STRING = "=";
+    private static final String PLUS_EQUALS_STRING = "+=";
+    private static final String COMMA_STRING =  ",";
+    private static final String PLUS_STRING =  "+";
+    /**
+     * Constructor
+     */
+    public Configurator()
+    {
+        this(Configuration.class);
+    }
+    
+    /**
+     * Constructor
+     */
+    public Configurator(Class<? extends Configuration> configurationClass)
+    {
+        this.configurationClass = configurationClass;
+        args = new LinkedHashMap<String, Object>();
+        more = new LinkedHashMap<String, Object>();
+        tokens = new TreeMap<String, String>();
+        
+        isConfigurationDirty = true;
+        configurationDefaultVariable = IFormatterSettingsConstants.FILES; // the default variable of the configuration.
+        configurationProblems = new ArrayList<ICompilerProblem>();
+
+        // initialize the localization manager.
+        LocalizationManager.get().addLocalizer(new ResourceBundleLocalizer());
+    }
+
+    private ConfigurationBuffer cfgbuf;
+    protected Configuration configuration;
+    private Class<? extends Configuration> configurationClass;
+    
+    private Map<String, Object> args, more;
+    private String[] extras;
+    private String configurationDefaultVariable;
+    
+    private Map<String, String> tokens;
+    
+    private boolean isConfigurationDirty;
+    private boolean configurationSuccess;
+    protected Collection<ICompilerProblem> configurationProblems;
+
+    // 
+    // IConfigurator related methods
+    //
+
+    public Collection<ICompilerProblem> validateConfiguration(String[] args)
+    {
+        if (args == null)
+            throw new NullPointerException("args may not be null");
+        
+        List<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
+        ConfigurationBuffer configurationBuffer = createConfigurationBuffer(configurationClass);
+        
+        try
+        {
+            CommandLineConfigurator.parse(configurationBuffer, null, args);
+        }
+        catch (ConfigurationException e)
+        {
+            final ICompilerProblem problem = new ConfigurationProblem(e);
+            problems.add(problem);
+        }
+        
+        return problems;
+    }
+    
+    public Collection<ICompilerProblem> getConfigurationProblems()
+    {
+        assert configuration != null : 
+            "Get the configuration problems after calling getConfiguration()";
+        
+        List<ICompilerProblem> problems = new ArrayList<ICompilerProblem>(configurationProblems.size() +
+                                            configuration.getConfigurationProblems().size());
+        problems.addAll(configurationProblems);
+        problems.addAll(configuration.getConfigurationProblems()); 
+        return problems;
+    }
+
+    public Configuration getConfiguration()
+    {
+        processConfiguration();
+        return configuration;
+    }
+    
+    public ConfigurationBuffer getConfigurationBuffer()
+    {
+        return cfgbuf;
+    }
+
+    /**
+     * Create a new configuration instance. The Configurator will need to 
+     * create a new configuration for each new configuration. For example,
+     * creating a new Configurator and getting the target settings will create
+     * a new configuration. If later on, the configuration is modified by calling
+     * any of the setter methods on the Configurator, then a new configuration 
+     * will be created the next time applyToProject() or getTargetSettings() is called.
+     * 
+     * The method may be overriden to allow for greater control when creating a 
+     * custom configuration that extends the built-in configuration.
+     * 
+     * @return a new configuration instance. If the custom configuration class
+     * cannot be created, the default configuration class will be created instead.
+     */
+    protected Configuration createConfiguration()
+    {
+        try
+        {
+            return configurationClass.newInstance();
+        }
+        catch (Exception e)
+        {
+            // If there is a problem initializing the configuration, then
+            // throw a ConfigurationException.
+            reportConfigurationException(new ConfigurationException.CouldNotInstantiate(configurationClass.getName()));
+
+            // Create the default configuration so we can report configuration
+            // problems.
+            try
+            {
+                return Configuration.class.newInstance();                
+            }
+            catch (Exception e2)
+            {
+                // this should never fail
+                assert(false);
+                return null;
+            }
+        }
+    }
+    
+    /**
+     * Initialize the configuration and the configuration buffer.
+     */
+    protected void initializeConfiguration()
+    {
+        // Create a clean configuration and configuration buffer
+        configuration = createConfiguration();
+        cfgbuf = createConfigurationBuffer(configuration.getClass());
+    }
+
+    /**
+     * Create a configuration buffer.
+     * @param configurationClass    The Configuration object
+     * @return          the configuration buffer to use
+     */
+    protected ConfigurationBuffer createConfigurationBuffer(
+            Class<? extends Configuration> configurationClass)
+    {
+        return new ConfigurationBuffer(
+                configurationClass, Configuration.getAliases());
+    }
+
+    /**
+     * Wrapper around the real processConfiguration.
+     * 
+     * @return true if success, false otherwise.
+     */
+    protected boolean processConfiguration()
+    {
+        boolean success = true;
+        
+        if (isConfigurationDirty)
+        {
+            configurationProblems.clear();
+            
+            try
+            {
+                success = processConfiguration(getOptions(args, more, processExtras(extras)));
+            }
+            catch (ConfigurationException e)
+            {
+                reportConfigurationException(e);
+                success = false;
+            }
+            catch (Exception e)
+            {
+            	e.printStackTrace();
+            }
+        }
+        else
+        {
+            success = configurationSuccess;
+        }
+        
+        isConfigurationDirty = false;
+        configurationSuccess = success;
+        return success;
+    }
+
+    /**
+     * Does all the work to set the command line arguments info the 
+     * configuration object.
+     *  
+     * @param argsArray - command line arguments
+     * 
+     * @return true if successful, false otherwise.
+     */
+    protected boolean  processConfiguration(String[] argsArray)
+    {
+        initializeConfiguration();
+        
+        boolean success = true;
+
+        try
+        {   
+            SystemPropertyConfigurator.load(cfgbuf, "royale");
+
+            // Parse the command line a first time, to peak at stuff like
+            // "royalelib" and "load-config".  The first parse is thrown
+            // away after that and we intentionally parse a second time
+            // below.  See note below.
+            CommandLineConfigurator.parse(cfgbuf, configurationDefaultVariable, argsArray);
+
+            overrideDefaults();
+
+            // Return if "-version" is present so the command line can print the 
+            // version.
+            if (cfgbuf.getVar("version") != null)
+                return false;
+
+            // Return so the command line can print help if "-help" is present.
+            final List<ConfigurationValue> helpVar = cfgbuf.getVar("help");
+            if (helpVar != null)
+                return false;
+            
+            // The command line needs to take precedence over all defaults and config files.
+            // By simply re-merging the command line back on top,
+            // we will get the behavior we want.
+            cfgbuf.clearSourceVars(CommandLineConfigurator.source);
+            CommandLineConfigurator.parse(cfgbuf, configurationDefaultVariable, argsArray);
+
+            // commit() reports problems instead of throwing an exception. This 
+            // allows us to process all the options in a configuration that
+            // are correct in the hopes that it will be enough to configure a
+            // project.
+            if (!cfgbuf.commit(configuration, configurationProblems))
+                success = false;
+            
+            configuration.validate(cfgbuf);
+        }
+        catch (ConfigurationException e)
+        {
+            reportConfigurationException(e);
+            success = false;
+        }
+        
+        return success;
+    }
+
+    /**
+     * Override default values.
+     */
+    protected void overrideDefaults() throws ConfigurationException
+    {
+    }
+
+    /**
+     * Convert conifguration exceptions to problems and collect them for 
+     * reporting.
+     * 
+     * @param e
+     */
+    protected void reportConfigurationException(ConfigurationException e)
+    {
+        final ICompilerProblem problem = new ConfigurationProblem(e);
+        configurationProblems.add(problem);
+    }
+    
+    //
+    // Configuration related methods
+    //
+    protected String[] getOptions(Map<String, Object> args, Map<String, Object> more, 
+		String[] extras)
+    {
+        ArrayList<String> buffer = new ArrayList<String>();
+        
+        for (Map.Entry<String, String> tokenEntry : tokens.entrySet())
+        {
+            buffer.add(PLUS_STRING + tokenEntry.getKey() + EQUALS_STRING + tokenEntry.getValue());
+        }
+        
+        for (Map.Entry<String, Object> arg : args.entrySet())
+        {
+            String key = arg.getKey();
+            Object value = arg.getValue();
+
+            if (value instanceof Boolean)
+            {
+                buffer.add(key + EQUALS_STRING + value);
+            }
+            else if (value instanceof Number)
+            {
+                buffer.add(key);
+                buffer.add(value.toString());
+            }
+            else if (value instanceof String)
+            {               
+                if (!"".equals(value))
+                {
+                    buffer.add(key);
+                    buffer.add((String)value);
+                }
+                else
+                {
+                    buffer.add(key + EQUALS_STRING);
+                }
+            }
+            else if (value instanceof File)
+            {
+                String p = ((File) value).getPath();
+                if (!"".equals(p))
+                {
+                    buffer.add(key);
+                    buffer.add(p);
+                }
+                else
+                {
+                    buffer.add(key + EQUALS_STRING);
+                }
+            }
+            else if (value instanceof java.util.Date)
+            {
+                buffer.add(key);
+                buffer.add(value.toString());
+            }
+            else if (value instanceof Map)
+            {
+                @SuppressWarnings("unchecked")
+                Map<String, ?> m = (Map<String, ?>) value;
+                for (Map.Entry<String, ?>entry : m.entrySet())
+                {
+                    String k = entry.getKey();
+                    Object v = entry.getValue();
+                    
+                    if (v instanceof String)
+                    {
+                        buffer.add(key);
+                        buffer.add(k);
+                        buffer.add((String)v);
+                    }
+                    else if (v instanceof File)
+                    {
+                        buffer.add(key);
+                        buffer.add(k);
+                        buffer.add(((File) v).getPath());
+                    }
+                    else if (v instanceof Collection)
+                    {
+                        buffer.add(key);
+                        buffer.add(k);
+                        Collection<?> list = (Collection<?>)v;
+                        for (Object next : list)
+                        {
+                            if (next != null)
+                                buffer.add(next.toString());
+                        }
+                    }
+                    else if (v != null)
+                    {
+                        assert false;
+                    }
+                }
+            }
+            else if (value instanceof int[])
+            {
+                int[] a = (int[]) value;
+                buffer.add(key);
+                buffer.add(String.valueOf(a[0]));
+                buffer.add(String.valueOf(a[1]));
+            }
+            else if (value instanceof Collection)
+            {
+                Collection<Object> list = new LinkedList<Object>((Collection<?>)args.get(key));
+                
+                int length = list.size();
+                if (length > 0)
+                {
+                    buffer.add(key);
+                }
+                for (Object obj : list)
+                {
+                    if (obj instanceof String)
+                    {
+                        buffer.add((String)obj);
+                    }
+                    else if (obj instanceof File)
+                    {
+                        buffer.add(((File)obj).getPath());
+                    }
+                }
+            }
+            else if (value != null)
+            {
+                assert false;
+            }
+            else
+            {
+                // System.err.println("unprocessed compiler options: " + key + EQUALS_STRING + value);
+            }
+        }
+        
+        for (Map.Entry<String, Object> moreEntry : more.entrySet())
+        {
+            String key = moreEntry.getKey();
+            Object value = moreEntry.getValue();
+
+            if (value instanceof Collection)
+            {
+                buffer.add(key + PLUS_EQUALS_STRING + toCommaSeparatedString((Collection<?>)value));
+            }
+            else if (value instanceof Map)
+            {
+                @SuppressWarnings("unchecked")
+                Map<String, ?> m = (Map<String, ?>) value;
+                for (Map.Entry<String, ?>entry : m.entrySet())
+                {
+                    String k = entry.getKey();
+                    Object v = entry.getValue();
+                    
+                    if (v instanceof Collection)
+                    {
+                        buffer.add(key + PLUS_EQUALS_STRING + k + COMMA_STRING +
+                                toCommaSeparatedString((Collection<?>)v));
+                    }
+                    else if (v != null)
+                    {
+                        assert false;
+                    }
+                }
+            }
+            else if (value != null)
+            {
+                assert false;
+            }
+            else
+            {
+                // System.err.println("unprocessed compiler options: " + key + EQUALS_STRING + value);
+            }
+        }
+
+        // Append extra command line args to the buffer.
+        if (extras != null && extras.length > 0)
+        {
+            for (int i = 0, length = extras == null ? 0 : extras.length; i < length; i++)
+            {
+                if (extras[i] != null)
+                {
+                    buffer.add(extras[i]);
+                }
+            }            
+        }
+        
+        String[] options = new String[buffer.size()];
+        buffer.toArray(options);
+    
+        if (Trace.config)
+            Trace.trace("Configurator: options = " + buffer.toString());
+        
+        return options;
+    }
+
+	protected String[] processExtras(String[] extraOptions) throws ConfigurationException
+    {
+		return extraOptions;
+	}
+
+    /**
+     * Sets the configuration parameters. The input should be valid <code>mxmlc/compc</code> command-line arguments.<p>
+     * 
+     * @param args <code>mxmlc/compc</code> command-line arguments
+     * @param defaultVariable the default variable of the configuration.
+     */
+    public void setConfiguration(String[] args, String defaultVariable)
+    {
+        extras = args;
+        configurationDefaultVariable = defaultVariable;
+        isConfigurationDirty = true;
+    }
+    
+    /**
+     * Defines a token. mxmlc and compc support token substitutions. For example,
+     * 
+     * <pre>
+     * mxmlc +royalelib=path1 +foo=bar --var=${foo}
+     * </pre>
+     * 
+     * <code>var=bar</code> after the substitution of <code>${foo}</code>.
+     * 
+     * @param name The name of the token.
+     * @param value The value of the token.
+     */
+    public void setToken(String name, String value)
+    {
+        tokens.put(name, value);
+        
+        isConfigurationDirty = true;
+    }
+
+    /**
+     * 
+     */
+    @Override
+    public String toString()
+    {
+        String[] options;
+        try
+        {
+            options = getOptions(args, more, processExtras(extras));
+        }
+        catch (ConfigurationException e)
+        {
+            options = new String[0];
+        }
+        
+        StringBuilder b = new StringBuilder();
+        for (int i = 0; i < options.length; i++)
+        {
+            b.append(options[i]);
+            b.append(' ');
+        }
+        return b.toString();
+    }
+    
+    private String toCommaSeparatedString(Collection<?> values)
+    {
+        StringBuilder b = new StringBuilder();
+        int length = values.size();
+        int i = 0;
+        for (Object value : values)
+        {
+            String valueString = null;
+            
+            if (value instanceof String)
+            {
+                valueString = (String)value;
+            }
+            else if (value instanceof File)
+            {
+                valueString = ((File)value).getPath();
+            }
+            
+            if (valueString != null)
+                b.append(valueString);
+            
+            if (i++ < length - 1)
+            {
+                b.append(COMMA_STRING);
+            }
+        }
+        return b.toString();
+    }
+    
+    @Override
+    public Configurator clone()
+    {
+        Configurator cloneConfig;
+        
+        try
+        {
+            cloneConfig = (Configurator) super.clone();
+        }
+        catch ( CloneNotSupportedException e )
+        {
+            throw new RuntimeException(e);//wont happen
+        }
+
+        cloneConfig.args = new LinkedHashMap<String, Object>(args);
+        cloneConfig.more = new LinkedHashMap<String, Object>(more);
+        cloneConfig.tokens = new LinkedHashMap<String, String>(tokens);
+        cloneConfig.configurationClass = configurationClass;
+        cloneConfig.isConfigurationDirty = true;
+        
+        if (extras != null)
+        {
+            cloneConfig.extras = new String[extras.length];
+            System.arraycopy(extras, 0, cloneConfig.extras, 0, extras.length);
+        }
+
+        return cloneConfig;
+    }
+}
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/IFormatterSettingsConstants.java b/formatter/src/main/java/org/apache/royale/formatter/config/IFormatterSettingsConstants.java
new file mode 100644
index 0000000..6650a90
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/IFormatterSettingsConstants.java
@@ -0,0 +1,24 @@
+/*
+ *
+ *  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.config;
+
+public interface IFormatterSettingsConstants {
+    static final String FILES                                       = "files";
+}
diff --git a/formatter/src/main/java/org/apache/royale/formatter/config/SystemPropertyConfigurator.java b/formatter/src/main/java/org/apache/royale/formatter/config/SystemPropertyConfigurator.java
new file mode 100644
index 0000000..b74fae0
--- /dev/null
+++ b/formatter/src/main/java/org/apache/royale/formatter/config/SystemPropertyConfigurator.java
@@ -0,0 +1,82 @@
+/*
+ *
+ *  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.config;
+
+import java.util.Properties;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+
+/**
+ * A utility class, which is used to load configuration options via
+ * system properties and populate a ConfigurationBuffer.  A
+ * counterpart of CommandLineConfigurator and FileConfigurator.
+ */
+public class SystemPropertyConfigurator
+{
+    /**
+     * Opportunistically find some configuration settings in system properties.
+     * @param buffer the intermediate config buffer
+     * @param prefix an optional prefix to add to the variable, pass null if no prefix
+     */
+    public static void load( final ConfigurationBuffer buffer, String prefix ) throws ConfigurationException
+    {
+        try
+        {
+            Properties props = System.getProperties();
+
+            for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();)
+            {
+                String propname = (String) e.nextElement();
+
+                if (!propname.startsWith( prefix + "."))
+                {
+                    String value = System.getProperty( propname );
+                    buffer.setToken( propname, value );
+                    continue;
+                }
+
+                String varname = propname.substring( prefix.length() + 1 );
+
+                if (!buffer.isValidVar( varname ))
+                    continue;
+
+                String value = System.getProperty( propname );
+
+                List<String> args = new LinkedList<String>();
+                StringTokenizer t = new StringTokenizer( value, "," );
+
+                while (t.hasMoreTokens())
+                {
+                    String token = t.nextToken();
+                    args.add( token );
+                }
+                buffer.setVar( varname, args, "system properties", -1 );
+            }
+        }
+        catch (SecurityException se)
+        {
+            // just ignore, this is an optional for loading configuration   
+        }
+    }
+}