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