| /* |
| * |
| * 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.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; |
| |
| /** |
| * 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 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) |
| 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 void setConfigurationPathResolver(IPathResolver pathResolver) |
| { |
| if (pathResolver == null) |
| throw new NullPointerException("pathResolver may not be null"); |
| |
| this.configurationPathResolver = pathResolver; |
| } |
| |
| 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()); |
| assert configurationPathResolver != null : "No configuration path resolver was set."; |
| configuration.setPathResolver(configurationPathResolver); |
| } |
| |
| /** |
| * 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; |
| |
| // 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, |
| // 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 |
| { |
| } |
| |
| /** |
| * 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. |
| * |
| * @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; |
| } |
| } |