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
+ }
+ }
+}