Allow property values to be restricted via command line flag.
The command line argument should be like this:
[-setProperty name=value,value...]
-setProperty flag can be used multiple times to set
different properties.
If a property is set multiple times, the last one overwrites
the others.
Change-Id: I9df49c81790a4eb77f3698b7c8cdcafe6dcb5460
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
index 217113a..6e7c460 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
@@ -25,6 +25,8 @@
import com.google.gwt.dev.util.arg.OptionOptimize;
import com.google.gwt.dev.util.arg.SourceLevel;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+import com.google.gwt.thirdparty.guava.common.collect.LinkedListMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import java.io.File;
@@ -45,6 +47,7 @@
private final boolean strictSourceResources;
private final OptionJsInteropMode.Mode jsInteropMode;
private final OptionMethodNameDisplayMode.Mode methodNameDisplayMode;
+ private final ListMultimap<String, String> properties;
CompilerOptionsImpl(CompileDir compileDir, String moduleName, Options options) {
this.compileDir = compileDir;
@@ -57,6 +60,7 @@
this.logLevel = options.getLogLevel();
this.jsInteropMode = options.getJsInteropMode();
this.methodNameDisplayMode = options.getMethodNameDisplayMode();
+ this.properties = LinkedListMultimap.create(options.getProperties());
}
@Override
@@ -109,11 +113,6 @@
return ImmutableList.of();
}
- @Override
- public OptionMethodNameDisplayMode.Mode getMethodNameDisplayMode() {
- return methodNameDisplayMode;
- }
-
/**
* Number of threads to use to compile permutations.
*/
@@ -133,6 +132,11 @@
}
@Override
+ public OptionMethodNameDisplayMode.Mode getMethodNameDisplayMode() {
+ return methodNameDisplayMode;
+ }
+
+ @Override
public File getMissingDepsFile() {
return null; // Don't record and save missing dependency information to a file.
}
@@ -163,6 +167,11 @@
}
@Override
+ public ListMultimap<String, String> getProperties() {
+ return properties;
+ }
+
+ @Override
public File getSaveSourceOutput() {
return null;
}
@@ -218,6 +227,11 @@
}
@Override
+ public boolean isIncrementalCompileEnabled() {
+ return incremental;
+ }
+
+ @Override
public boolean isJsonSoycEnabled() {
return false;
}
@@ -274,11 +288,6 @@
}
@Override
- public boolean isIncrementalCompileEnabled() {
- return incremental;
- }
-
- @Override
public boolean shouldInlineLiteralParameters() {
return false;
}
@@ -319,12 +328,12 @@
}
@Override
- public boolean warnOverlappingSource() {
+ public boolean warnMissingDeps() {
return false;
}
@Override
- public boolean warnMissingDeps() {
+ public boolean warnOverlappingSource() {
return false;
}
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
index 2c6e8dd..66e5348 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
@@ -23,15 +23,19 @@
import com.google.gwt.dev.util.arg.ArgHandlerJsInteropMode;
import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
import com.google.gwt.dev.util.arg.ArgHandlerMethodNameDisplayMode;
+import com.google.gwt.dev.util.arg.ArgHandlerSetProperties;
import com.google.gwt.dev.util.arg.ArgHandlerSourceLevel;
import com.google.gwt.dev.util.arg.OptionIncrementalCompile;
import com.google.gwt.dev.util.arg.OptionJsInteropMode;
import com.google.gwt.dev.util.arg.OptionLogLevel;
import com.google.gwt.dev.util.arg.OptionMethodNameDisplayMode;
+import com.google.gwt.dev.util.arg.OptionSetProperties;
import com.google.gwt.dev.util.arg.OptionSourceLevel;
import com.google.gwt.dev.util.arg.SourceLevel;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
+import com.google.gwt.thirdparty.guava.common.collect.LinkedListMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
import com.google.gwt.util.tools.ArgHandler;
import com.google.gwt.util.tools.ArgHandlerDir;
import com.google.gwt.util.tools.ArgHandlerExtra;
@@ -82,6 +86,8 @@
private OptionMethodNameDisplayMode.Mode methodNameDisplayMode =
OptionMethodNameDisplayMode.Mode.NONE;
+ private final ListMultimap<String, String> properties = LinkedListMultimap.create();
+
/**
* Sets each option to the appropriate value, based on command-line arguments.
* If there is an error, prints error messages and/or usage to System.err.
@@ -299,6 +305,10 @@
return jsInteropMode;
}
+ ListMultimap<String, String> getProperties() {
+ return properties;
+ }
+
private class ArgProcessor extends ArgProcessorBase {
public ArgProcessor() {
@@ -314,6 +324,19 @@
registerHandler(new StrictResourcesFlag());
registerHandler(new WorkDirFlag());
registerHandler(new LauncherDir());
+ registerHandler(new ArgHandlerSetProperties(new OptionSetProperties() {
+
+ @Override
+ public void setPropertyValues(String name, Iterable<String> values) {
+ properties.replaceValues(name, values);
+ }
+
+ @Override
+ public ListMultimap<String, String> getProperties() {
+ return properties;
+ }
+
+ }));
registerHandler(new ArgHandlerIncrementalCompile(new OptionIncrementalCompile() {
@Override
public boolean isIncrementalCompileEnabled() {
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
index 21fceb4..0e86063 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -297,6 +297,10 @@
compilerContext = compilerContextBuilder.options(loadOptions).build();
ModuleDef module = loadModule(compileLogger);
+ if (!Compiler.maybeRestrictProperties(compileLogger, module,
+ loadOptions.getProperties())) {
+ return false;
+ }
// We need to generate the stub before restricting permutations
String recompileJs = generateModuleRecompileJs(module, compileLogger);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
index 312881a..9576991 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/UnmodifiableCompilerOptions.java
@@ -299,4 +299,9 @@
public void setMethodNameDisplayMode(OptionMethodNameDisplayMode.Mode methodNameDisplayMode) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void setPropertyValues(String name, Iterable<String> value) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java
index d519851..238aa8d 100644
--- a/dev/core/src/com/google/gwt/dev/Compiler.java
+++ b/dev/core/src/com/google/gwt/dev/Compiler.java
@@ -19,6 +19,8 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.javac.UnitCache;
@@ -37,18 +39,22 @@
import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
import com.google.gwt.dev.util.arg.ArgHandlerMethodNameDisplayMode;
import com.google.gwt.dev.util.arg.ArgHandlerSaveSourceOutput;
+import com.google.gwt.dev.util.arg.ArgHandlerSetProperties;
import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
import com.google.gwt.dev.util.arg.OptionOptimize;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.util.tools.Utility;
import java.io.File;
import java.io.IOException;
+import java.util.Collection;
import java.util.List;
+import java.util.Map.Entry;
import java.util.concurrent.FutureTask;
/**
@@ -71,6 +77,7 @@
registerHandler(new ArgHandlerExtraDir(options));
registerHandler(new ArgHandlerSaveSourceOutput(options));
registerHandler(new ArgHandlerMethodNameDisplayMode(options));
+ registerHandler(new ArgHandlerSetProperties(options));
}
@Override
@@ -153,7 +160,12 @@
ModuleDef[] modules = new ModuleDef[options.getModuleNames().size()];
int i = 0;
for (String moduleName : options.getModuleNames()) {
- modules[i++] = ModuleDefLoader.loadFromClassPath(logger, compilerContext, moduleName, true);
+ ModuleDef module =
+ ModuleDefLoader.loadFromClassPath(logger, compilerContext, moduleName, true);
+ modules[i++] = module;
+ if (!maybeRestrictProperties(logger, module, options.getProperties())) {
+ return false;
+ }
}
return run(logger, modules);
}
@@ -264,4 +276,43 @@
}
return true;
}
+
+ public static boolean maybeRestrictProperties(TreeLogger logger, ModuleDef module,
+ ListMultimap<String, String> properties) {
+ try {
+ for (Entry<String, Collection<String>> property : properties.asMap().entrySet()) {
+ String propertyName = property.getKey();
+ Collection<String> propertyValues = property.getValue();
+ BindingProperty bindingProp = module.getProperties().findBindingProp(propertyName);
+ ConfigurationProperty configProp = module.getProperties().findConfigProp(propertyName);
+ if (bindingProp != null) {
+ bindingProp.setValues(bindingProp.getRootCondition(),
+ propertyValues.toArray(new String[propertyValues.size()]));
+ } else if (configProp != null) {
+ if (configProp.allowsMultipleValues()) {
+ configProp.clear();
+ for (String propertyValue : propertyValues) {
+ configProp.addValue(propertyValue);
+ }
+ } else {
+ String firstValue = propertyValues.iterator().next();
+ if (propertyValues.size() > 1) {
+ logger.log(TreeLogger.ERROR,
+ "Attemp to set multiple values to a single-valued configuration property '"
+ + propertyName + "'.");
+ return false;
+ }
+ configProp.setValue(firstValue);
+ }
+ } else {
+ logger.log(TreeLogger.ERROR, "Unknown property: " + propertyName);
+ return false;
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ logger.log(TreeLogger.ERROR, e.getMessage());
+ return false;
+ }
+ return true;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/DevMode.java b/dev/core/src/com/google/gwt/dev/DevMode.java
index f55e144..f2aeea7 100644
--- a/dev/core/src/com/google/gwt/dev/DevMode.java
+++ b/dev/core/src/com/google/gwt/dev/DevMode.java
@@ -40,6 +40,7 @@
import com.google.gwt.dev.util.arg.ArgHandlerMethodNameDisplayMode;
import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
import com.google.gwt.dev.util.arg.ArgHandlerModulePathPrefix;
+import com.google.gwt.dev.util.arg.ArgHandlerSetProperties;
import com.google.gwt.dev.util.arg.ArgHandlerSourceLevel;
import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
@@ -253,6 +254,7 @@
return super.getPurpose() + " to host";
}
});
+ registerHandler(new ArgHandlerSetProperties(options));
}
@Override
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptions.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptions.java
index 8475edf..f4e990a 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptions.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptions.java
@@ -22,6 +22,7 @@
import com.google.gwt.dev.util.arg.OptionMaxPermsPerPrecompile;
import com.google.gwt.dev.util.arg.OptionMissingDepsFile;
import com.google.gwt.dev.util.arg.OptionSaveSource;
+import com.google.gwt.dev.util.arg.OptionSetProperties;
import com.google.gwt.dev.util.arg.OptionSourceMapFilePrefix;
import com.google.gwt.dev.util.arg.OptionValidateOnly;
import com.google.gwt.dev.util.arg.OptionWarnMissingDeps;
@@ -33,5 +34,6 @@
public interface PrecompileTaskOptions extends JJSOptions, CompileTaskOptions, OptionGenDir,
OptionSaveSource, OptionSourceMapFilePrefix, OptionValidateOnly, OptionDisableUpdateCheck,
OptionEnableGeneratingOnShards, OptionMaxPermsPerPrecompile, OptionMissingDepsFile,
- OptionWarnOverlappingSource, OptionWarnMissingDeps, PrecompilationResult {
+ OptionWarnOverlappingSource, OptionWarnMissingDeps, PrecompilationResult,
+ OptionSetProperties {
}
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
index c73a08c..a958425 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
@@ -23,6 +23,8 @@
import com.google.gwt.dev.util.arg.OptionJsInteropMode;
import com.google.gwt.dev.util.arg.OptionMethodNameDisplayMode;
import com.google.gwt.dev.util.arg.SourceLevel;
+import com.google.gwt.thirdparty.guava.common.collect.LinkedListMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
import java.io.File;
@@ -42,6 +44,7 @@
private boolean validateOnly;
private boolean warnOverlappingSource;
private boolean warnMissingDeps;
+ private final ListMultimap<String, String> properties = LinkedListMultimap.create();
public PrecompileTaskOptionsImpl() {
}
@@ -77,6 +80,7 @@
setMissingDepsFile(other.getMissingDepsFile());
setValidateOnly(other.isValidateOnly());
setEnabledGeneratingOnShards(other.isEnabledGeneratingOnShards());
+ properties.putAll(other.getProperties());
}
@Override
@@ -104,6 +108,10 @@
return genDir;
}
+ @Override public OptionJsInteropMode.Mode getJsInteropMode() {
+ return jjsOptions.getJsInteropMode();
+ }
+
@Override
public int getMaxPermsPerPrecompile() {
return maxPermsPerPrecompile;
@@ -135,8 +143,12 @@
}
@Override
- public SourceLevel getSourceLevel()
- {
+ public ListMultimap<String, String> getProperties() {
+ return properties;
+ }
+
+ @Override
+ public SourceLevel getSourceLevel() {
return jjsOptions.getSourceLevel();
}
@@ -180,6 +192,11 @@
}
@Override
+ public boolean isIncrementalCompileEnabled() {
+ return jjsOptions.isIncrementalCompileEnabled();
+ }
+
+ @Override
public boolean isJsonSoycEnabled() {
return jjsOptions.isJsonSoycEnabled();
}
@@ -250,11 +267,6 @@
}
@Override
- public void setIncrementalCompileEnabled(boolean enabled) {
- jjsOptions.setIncrementalCompileEnabled(enabled);
- }
-
- @Override
public void setCompilerMetricsEnabled(boolean enabled) {
jjsOptions.setCompilerMetricsEnabled(enabled);
}
@@ -300,10 +312,19 @@
}
@Override
+ public void setIncrementalCompileEnabled(boolean enabled) {
+ jjsOptions.setIncrementalCompileEnabled(enabled);
+ }
+
+ @Override
public void setInlineLiteralParameters(boolean enabled) {
jjsOptions.setInlineLiteralParameters(enabled);
}
+ @Override public void setJsInteropMode(OptionJsInteropMode.Mode mode) {
+ jjsOptions.setJsInteropMode(mode);
+ }
+
@Override
public void setJsonSoycEnabled(boolean enabled) {
jjsOptions.setJsonSoycEnabled(enabled);
@@ -360,6 +381,11 @@
}
@Override
+ public void setPropertyValues(String name, Iterable<String> values) {
+ properties.replaceValues(name, values);
+ }
+
+ @Override
public void setRunAsyncEnabled(boolean enabled) {
jjsOptions.setRunAsyncEnabled(enabled);
}
@@ -410,13 +436,13 @@
}
@Override
- public void setWarnOverlappingSource(boolean warnOverlappingSource) {
- this.warnOverlappingSource = warnOverlappingSource;
+ public void setWarnMissingDeps(boolean showMissingDepsWarnings) {
+ this.warnMissingDeps = showMissingDepsWarnings;
}
@Override
- public void setWarnMissingDeps(boolean showMissingDepsWarnings) {
- this.warnMissingDeps = showMissingDepsWarnings;
+ public void setWarnOverlappingSource(boolean warnOverlappingSource) {
+ this.warnOverlappingSource = warnOverlappingSource;
}
@Override
@@ -430,11 +456,6 @@
}
@Override
- public boolean isIncrementalCompileEnabled() {
- return jjsOptions.isIncrementalCompileEnabled();
- }
-
- @Override
public boolean shouldInlineLiteralParameters() {
return jjsOptions.shouldInlineLiteralParameters();
}
@@ -448,7 +469,6 @@
public boolean shouldOptimizeDataflow() {
return jjsOptions.shouldOptimizeDataflow();
}
-
@Override
public boolean shouldOrdinalizeEnums() {
return jjsOptions.shouldOrdinalizeEnums();
@@ -468,21 +488,14 @@
public boolean useDetailedTypeIds() {
return jjsOptions.useDetailedTypeIds();
}
- @Override
- public boolean warnOverlappingSource() {
- return warnOverlappingSource;
- }
@Override
public boolean warnMissingDeps() {
return warnMissingDeps;
}
- @Override public OptionJsInteropMode.Mode getJsInteropMode() {
- return jjsOptions.getJsInteropMode();
- }
-
- @Override public void setJsInteropMode(OptionJsInteropMode.Mode mode) {
- jjsOptions.setJsInteropMode(mode);
+ @Override
+ public boolean warnOverlappingSource() {
+ return warnOverlappingSource;
}
}
diff --git a/dev/core/src/com/google/gwt/dev/shell/SuperDevListener.java b/dev/core/src/com/google/gwt/dev/shell/SuperDevListener.java
index 27614e4..d266e2a 100644
--- a/dev/core/src/com/google/gwt/dev/shell/SuperDevListener.java
+++ b/dev/core/src/com/google/gwt/dev/shell/SuperDevListener.java
@@ -24,6 +24,8 @@
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.util.arg.OptionJsInteropMode;
import com.google.gwt.dev.util.arg.OptionMethodNameDisplayMode;
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.util.concurrent.SettableFuture;
import java.io.File;
@@ -207,9 +209,26 @@
args.add("-XmethodNameDisplayMode");
args.add(options.getMethodNameDisplayMode().name());
}
+ if (options.getProperties().size() > 0) {
+ args.addAll(makeSetPropertyArgs(options.getProperties()));
+ }
for (String mod : options.getModuleNames()) {
args.add(mod);
}
return args;
}
+
+ private static List<String> makeSetPropertyArgs(
+ ListMultimap<String, String> properties) {
+ List<String> propertyArgs = Lists.newArrayList();
+ for (String propertyName : properties.keySet()) {
+ propertyArgs.add("-setProperty");
+ StringBuilder nameValues = new StringBuilder(propertyName + "=");
+ for (String propertyValue : properties.get(propertyName)) {
+ nameValues.append(propertyValue + ",");
+ }
+ propertyArgs.add(nameValues.substring(0, nameValues.length() - 1));
+ }
+ return propertyArgs;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerSetProperties.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerSetProperties.java
new file mode 100644
index 0000000..3f8bb1f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerSetProperties.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed 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 com.google.gwt.dev.util.arg;
+
+import com.google.gwt.thirdparty.guava.common.base.Splitter;
+import com.google.gwt.util.tools.ArgHandlerString;
+
+import java.util.List;
+
+/**
+ * An argument handler to parse the -setProperty argument. Set the allowed values of a given
+ * property to the given values. The format is like: [-setProperty name=value,value,...,value].
+ * -setProperty flag can be used multiple times to set different properties. If a property is set
+ * multiple times, the last one over writes the others.
+ */
+public class ArgHandlerSetProperties extends ArgHandlerString {
+
+ private final OptionSetProperties options;
+
+ public ArgHandlerSetProperties(OptionSetProperties options) {
+ this.options = options;
+ }
+
+ @Override
+ public boolean setString(String str) {
+ assert (str != null);
+ List<String> nameValuePair = Splitter.on("=").trimResults().omitEmptyStrings().splitToList(str);
+ if (nameValuePair.size() != 2) {
+ return false;
+ }
+ String name = nameValuePair.get(0);
+ String valuesList = nameValuePair.get(1);
+ Iterable<String> values = Splitter.on(",").trimResults().omitEmptyStrings().split(valuesList);
+ options.setPropertyValues(name, values);
+ return true;
+ }
+
+ @Override
+ public String getPurpose() {
+ return "Set the values of a property in the form of propertyName=value1[,value2...].";
+ }
+
+ @Override
+ public String getTag() {
+ return "-setProperty";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"name=value,value..."};
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionSetProperties.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionSetProperties.java
new file mode 100644
index 0000000..4ef915e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionSetProperties.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed 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 com.google.gwt.dev.util.arg;
+
+import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
+
+/**
+ * Option to set property values.
+ */
+public interface OptionSetProperties {
+
+ /**
+ * Set the value of property {@code name} to be {@code values}.
+ */
+ void setPropertyValues(String name, Iterable<String> values);
+
+ /**
+ * Return all properties ({name->values} pairs).
+ */
+ ListMultimap<String, String> getProperties();
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/ArgHandlerSetPropertiesTest.java b/dev/core/test/com/google/gwt/dev/ArgHandlerSetPropertiesTest.java
new file mode 100644
index 0000000..f55c2c5
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/ArgHandlerSetPropertiesTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed 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 com.google.gwt.dev;
+
+import java.util.Collection;
+
+/**
+ * Test for {@ArgHandlerSetProperties}.
+ */
+public class ArgHandlerSetPropertiesTest extends ArgProcessorTestBase {
+ private final Compiler.ArgProcessor argProcessor;
+ private final CompilerOptionsImpl options = new CompilerOptionsImpl();
+
+ public ArgHandlerSetPropertiesTest() {
+ argProcessor = new Compiler.ArgProcessor(options);
+ }
+
+ public void testSinglePropertySingleValue() {
+ assertProcessSuccess(argProcessor,
+ new String[] {"-setProperty", "locale=zh", "my.Module"});
+ assertEquals("{locale=[zh]}", options.getProperties().toString());
+ }
+
+ public void testMultiplePropertiesSingleValue() {
+ assertProcessSuccess(argProcessor, new String[] {
+ "-setProperty", "locale=zh",
+ "-setProperty", "user.agent=safari",
+ "-setProperty", "stackTraces=false", "my.Module"});
+ assertEquals(3, options.getProperties().keySet().size());
+ assertEquals("[zh]", options.getProperties().get("locale").toString());
+ assertEquals("[safari]", options.getProperties().get("user.agent").toString());
+ assertEquals("[false]", options.getProperties().get("stackTraces").toString());
+ }
+
+ public void testSinglePropertyMultipleValues() {
+ assertProcessSuccess(argProcessor,
+ new String[] {"-setProperty", "locale=zh,en", "my.Module"});
+ assertEquals(1, options.getProperties().keySet().size());
+ Collection<String> locales = options.getProperties().get("locale");
+ assertEquals(2, locales.size());
+ assertTrue(locales.contains("zh") && locales.contains("en"));
+ }
+
+ public void testMultiplePropertiesMultipleValues() {
+ assertProcessSuccess(argProcessor, new String[] {
+ "-setProperty", "locale=zh,en",
+ "-setProperty", "user.agent=safari,opera",
+ "-setProperty", "stackTraces=true,false",
+ "my.Module"});
+ assertEquals(3, options.getProperties().keySet().size());
+
+ Collection<String> locales = options.getProperties().get("locale");
+ Collection<String> userAgents = options.getProperties().get("user.agent");
+ Collection<String> stackTraces = options.getProperties().get("stackTraces");
+ assertEquals(2, locales.size());
+ assertTrue(locales.contains("zh") && locales.contains("en"));
+ assertEquals(2, userAgents.size());
+ assertTrue(userAgents.contains("opera") && userAgents.contains("safari"));
+ assertEquals(2, stackTraces.size());
+ assertTrue(stackTraces.contains("false") && stackTraces.contains("true"));
+ }
+
+ public void testSetOnePropertyMultipleTimes() {
+ assertProcessSuccess(argProcessor, new String[] {
+ "-setProperty", "locale = zh",
+ "-setProperty", "locale = en, fr",
+ "my.Module"
+ });
+ assertEquals(1, options.getProperties().keySet().size());
+ Collection<String> locales = options.getProperties().get("locale");
+ assertEquals(2, locales.size());
+ assertTrue(locales.contains("en") && locales.contains("fr"));
+ }
+}