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 86a7f863063d69e0c1b3841228ec827380cb84be Author: Josh Tynjala <joshtynj...@apache.org> AuthorDate: Mon Sep 26 09:55:44 2022 -0700 formatter: load-config option and asformat-config.xml detection --- .../org/apache/royale/formatter/FORMATTER.java | 5 +- .../royale/formatter/config/Configuration.java | 148 +++++ .../royale/formatter/config/Configurator.java | 156 +++++ .../internal/config/FileConfigurator.java | 680 +++++++++++++++++++++ .../config/SystemPropertyConfigurator.java | 3 +- 5 files changed, 990 insertions(+), 2 deletions(-) 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 e6ad9be4b..29fd7dd1c 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.ProblemPrinter; 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 @@ public class FORMATTER { 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 @@ public class FORMATTER { // 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 dd5c0acdb..d3ee23a3e 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.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; +import org.apache.royale.utils.FilenameNormalization; + +import com.google.common.collect.ImmutableList; public class Configuration { @@ -49,6 +55,23 @@ public class Configuration { } 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. @@ -143,6 +166,31 @@ public class Configuration { { } + // + // '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 @@ public class Configuration { { 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 c2abb488c..26ab6653b 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 @@ package org.apache.royale.formatter.config; 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 @@ public class Configurator implements Cloneable 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 @@ public class Configurator implements Cloneable 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 @@ public class Configurator implements Cloneable // 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 @@ public class Configurator implements Cloneable 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, @@ -356,6 +393,125 @@ public class Configurator implements Cloneable { } + /** + * 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 000000000..79fc4474d --- /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 96% 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 b74fae013..d32b4c45e 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.LinkedList; 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