blob: fa76101abbc6a0211fc94e5dd1b9ecf096b4c93b [file] [log] [blame]
/*
* Copyright 2008 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.cfg;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.Linker;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.dev.js.JsParser;
import com.google.gwt.dev.js.JsParserException;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.util.Empty;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.xml.AttributeConverter;
import com.google.gwt.dev.util.xml.Schema;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
// CHECKSTYLE_NAMING_OFF
/**
* Configures a module definition object using XML.
*/
public class ModuleDefSchema extends Schema {
private final class BodySchema extends Schema {
protected final String __add_linker_1_name = null;
protected final String __clear_configuration_property_1_name = null;
protected final String __collapse_all_properties_1_value = "true";
protected final String __collapse_property_1_name = null;
protected final String __collapse_property_2_values = null;
protected final String __define_configuration_property_1_name = null;
protected final String __define_configuration_property_2_is_multi_valued = null;
protected final String __define_linker_1_name = null;
protected final String __define_linker_2_class = null;
protected final String __define_property_1_name = null;
protected final String __define_property_2_values = null;
protected final String __entry_point_1_class = null;
protected final String __extend_configuration_property_1_name = null;
protected final String __extend_configuration_property_2_value = null;
protected final String __extend_property_1_name = null;
protected final String __extend_property_2_values = null;
protected final String __generate_with_1_class = null;
protected final String __inherits_1_name = null;
protected final String __property_provider_1_name = null;
protected final String __public_1_path = null;
protected final String __public_2_includes = "";
protected final String __public_3_excludes = "";
protected final String __public_4_skips = "";
protected final String __public_5_defaultexcludes = "yes";
protected final String __public_6_casesensitive = "true";
protected final String __replace_with_1_class = null;
protected final String __script_1_src = null;
protected final String __servlet_1_path = null;
protected final String __servlet_2_class = null;
protected final String __set_configuration_property_1_name = null;
protected final String __set_configuration_property_2_value = null;
protected final String __set_property_1_name = null;
protected final String __set_property_2_value = null;
protected final String __set_property_fallback_1_name = null;
protected final String __set_property_fallback_2_value = null;
protected final String __source_1_path = "";
protected final String __source_2_includes = "";
protected final String __source_3_excludes = "";
protected final String __source_4_skips = "";
protected final String __source_5_defaultexcludes = "yes";
protected final String __source_6_casesensitive = "true";
protected final String __stylesheet_1_src = null;
protected final String __super_source_1_path = "";
protected final String __super_source_2_includes = "";
protected final String __super_source_3_excludes = "";
protected final String __super_source_4_skips = "";
protected final String __super_source_5_defaultexcludes = "yes";
protected final String __super_source_6_casesensitive = "true";
/**
* Used to accumulate binding property conditions before recording the
* newly-allowed values.
*/
private ConditionAll bindingPropertyCondition;
private Schema fChild;
protected Schema __add_linker_begin(LinkerName name)
throws UnableToCompleteException {
if (moduleDef.getLinker(name.name) == null) {
Messages.LINKER_NAME_INVALID.log(logger, name.name, null);
throw new UnableToCompleteException();
}
moduleDef.addLinker(name.name);
return null;
}
protected Schema __clear_configuration_property_begin(PropertyName name)
throws UnableToCompleteException {
// Don't allow configuration properties with the same name as a
// deferred-binding property.
Property prop = moduleDef.getProperties().find(name.token);
if (prop == null) {
logger.log(TreeLogger.ERROR, "No property named " + name.token
+ " has been defined");
throw new UnableToCompleteException();
} else if (!(prop instanceof ConfigurationProperty)) {
if (prop instanceof BindingProperty) {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " is already defined as a deferred-binding property");
} else {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " is already defined as a property of unknown type");
}
throw new UnableToCompleteException();
}
((ConfigurationProperty) prop).clear();
// No children.
return null;
}
protected Schema __collapse_all_properties_begin(boolean collapse) {
moduleDef.setCollapseAllProperties(collapse);
return null;
}
protected Schema __collapse_property_begin(PropertyName name,
PropertyValueGlob[] values) throws UnableToCompleteException {
Property prop = moduleDef.getProperties().find(name.token);
if (prop == null) {
logger.log(TreeLogger.ERROR, "No property named " + name.token
+ " has been defined");
throw new UnableToCompleteException();
} else if (!(prop instanceof BindingProperty)) {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " is not a deferred-binding property");
throw new UnableToCompleteException();
}
BindingProperty binding = (BindingProperty) prop;
List<String> allowed = Arrays.asList(binding.getDefinedValues());
String[] tokens = new String[values.length];
boolean error = false;
for (int i = 0, j = values.length; i < j; i++) {
tokens[i] = values[i].token;
if (values[i].isGlob()) {
// Expanded later in BindingProperty
continue;
} else if (!allowed.contains(tokens[i])) {
logger.log(TreeLogger.ERROR, "The value " + tokens[i]
+ " was not previously defined for the property "
+ binding.getName());
error = true;
}
}
if (error) {
throw new UnableToCompleteException();
}
binding.addCollapsedValues(tokens);
// No children
return null;
}
protected Schema __define_configuration_property_begin(PropertyName name,
String is_multi_valued) throws UnableToCompleteException {
boolean isMultiValued = toPrimitiveBoolean(is_multi_valued);
// Don't allow configuration properties with the same name as a
// deferred-binding property.
Property existingProperty = moduleDef.getProperties().find(name.token);
if (existingProperty == null) {
// Create the property
moduleDef.getProperties().createConfiguration(name.token, isMultiValued);
if (!propertyDefinitions.containsKey(name.token)) {
propertyDefinitions.put(name.token, moduleName);
}
} else if (existingProperty instanceof ConfigurationProperty) {
// Allow redefinition only if the 'is-multi-valued' setting is identical
// The previous definition may have been explicit, via
// <define-configuration-property>, or implicit, via
// <set-configuration-property>. In the latter case, the
// value of the 'is-multi-valued' attribute was taken as false.
String originalDefinition = propertyDefinitions.get(name.token);
if (((ConfigurationProperty) existingProperty).allowsMultipleValues() != isMultiValued) {
if (originalDefinition != null) {
logger.log(
TreeLogger.ERROR,
"The configuration property named "
+ name.token
+ " is already defined with a different 'is-multi-valued' setting");
} else {
logger.log(
TreeLogger.ERROR,
"The configuration property named "
+ name.token
+ " is already defined implicitly by 'set-configuration-property'"
+ " in " + propertySettings.get(name.token)
+ " with 'is-multi-valued' set to 'false'");
}
throw new UnableToCompleteException();
} else {
if (originalDefinition != null) {
logger.log(TreeLogger.WARN,
"Ignoring identical definition of the configuration property named "
+ name.token + " (originally defined in "
+ originalDefinition + ", redefined in " + moduleName + ")");
} else {
logger.log(TreeLogger.WARN,
"Definition of already set configuration property named "
+ name.token + " in " + moduleName + " (set in "
+ propertySettings.get(name.token)
+ "). This may be disallowed in the future.");
}
}
} else {
if (existingProperty instanceof BindingProperty) {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " is already defined as a deferred-binding property");
} else {
// Future proofing if other subclasses are added.
logger.log(TreeLogger.ERROR, "May not replace property named "
+ name.token + " of unknown type "
+ existingProperty.getClass().getName());
}
throw new UnableToCompleteException();
}
// No children.
return null;
}
protected Schema __define_linker_begin(LinkerName name,
Class<? extends Linker> linker) throws UnableToCompleteException {
if (!Linker.class.isAssignableFrom(linker)) {
logger.log(TreeLogger.ERROR, "A linker must extend "
+ Linker.class.getName(), null);
throw new UnableToCompleteException();
}
if (linker.getAnnotation(LinkerOrder.class) == null) {
logger.log(TreeLogger.ERROR, "Linkers must be annotated with the "
+ LinkerOrder.class.getName() + " annotation", null);
throw new UnableToCompleteException();
}
moduleDef.defineLinker(logger, name.name, linker);
return null;
}
protected Schema __define_property_begin(PropertyName name,
PropertyValue[] values) throws UnableToCompleteException {
Property existingProperty = moduleDef.getProperties().find(name.token);
if (existingProperty != null) {
// Disallow redefinition of properties, but provide a type-sensitive
// error message to aid in diagnosis.
if (existingProperty instanceof BindingProperty) {
logger.log(TreeLogger.ERROR, "The deferred-binding property named "
+ name.token + " may not be redefined.");
} else if (existingProperty instanceof ConfigurationProperty) {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " is already defined as a configuration property");
} else {
// Future proofing if other subclasses are added.
logger.log(TreeLogger.ERROR, "May not replace property named "
+ name.token + " of unknown type "
+ existingProperty.getClass().getName());
}
throw new UnableToCompleteException();
}
BindingProperty prop = moduleDef.getProperties().createBinding(name.token);
for (int i = 0; i < values.length; i++) {
prop.addDefinedValue(prop.getRootCondition(), values[i].token);
}
// No children.
return null;
}
protected Schema __entry_point_begin(String className) {
moduleDef.addEntryPointTypeName(className);
return null;
}
protected Schema __extend_configuration_property_begin(PropertyName name,
String value) throws UnableToCompleteException {
// Property must already exist as a configuration property
Property prop = moduleDef.getProperties().find(name.token);
if ((prop == null) || !(prop instanceof ConfigurationProperty)) {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " must already exist as a configuration property");
throw new UnableToCompleteException();
}
ConfigurationProperty configProp = (ConfigurationProperty) prop;
if (!configProp.allowsMultipleValues()) {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " does not support multiple values");
throw new UnableToCompleteException();
}
configProp.addValue(value);
return null;
}
protected Schema __extend_property_begin(BindingProperty property,
PropertyValue[] values) {
for (int i = 0; i < values.length; i++) {
property.addDefinedValue(property.getRootCondition(), values[i].token);
}
// No children.
return null;
}
protected Schema __fail_begin() {
RuleFail rule = new RuleFail();
moduleDef.getRules().prepend(rule);
return new FullConditionSchema(rule.getRootCondition());
}
protected Schema __generate_with_begin(Class<? extends Generator> generator)
throws UnableToCompleteException {
if (!Generator.class.isAssignableFrom(generator)) {
logger.log(TreeLogger.ERROR, "A generator must extend "
+ Generator.class.getName(), null);
throw new UnableToCompleteException();
}
RuleGenerateWith rule = new RuleGenerateWith(generator);
moduleDef.getRules().prepend(rule);
return new FullConditionSchema(rule.getRootCondition());
}
protected Schema __inherits_begin(String name)
throws UnableToCompleteException {
TreeLogger branch = logger.branch(TreeLogger.TRACE,
"Loading inherited module '" + name + "'", null);
loader.nestedLoad(branch, name, moduleDef);
return null;
}
@SuppressWarnings("unused")
protected Schema __property_provider_begin(BindingProperty property) {
return fChild = new ScriptSchema();
}
protected void __property_provider_end(BindingProperty property)
throws UnableToCompleteException {
ScriptSchema childSchema = ((ScriptSchema) fChild);
String script = childSchema.getScript();
if (script == null) {
// This is a problem.
//
logger.log(TreeLogger.ERROR,
"Property providers must specify a JavaScript body", null);
throw new UnableToCompleteException();
}
int lineNumber = childSchema.getStartLineNumber();
JsFunction fn = parseJsBlock(lineNumber, script);
property.setProvider(new PropertyProvider(fn.getBody().toSource()));
}
@SuppressWarnings("unused")
protected Schema __public_begin(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
return fChild = new IncludeExcludeSchema();
}
protected void __public_end(String path, String includes, String excludes,
String skips, String defaultExcludes, String caseSensitive) {
IncludeExcludeSchema childSchema = ((IncludeExcludeSchema) fChild);
foundAnyPublic = true;
Set<String> includeSet = childSchema.getIncludes();
addDelimitedStringToSet(includes, "[ ,]", includeSet);
String[] includeList = includeSet.toArray(new String[includeSet.size()]);
Set<String> excludeSet = childSchema.getExcludes();
addDelimitedStringToSet(excludes, "[ ,]", excludeSet);
String[] excludeList = excludeSet.toArray(new String[excludeSet.size()]);
Set<String> skipSet = childSchema.getSkips();
addDelimitedStringToSet(skips, "[ ,]", skipSet);
String[] skipList = skipSet.toArray(new String[skipSet.size()]);
boolean doDefaultExcludes = toPrimitiveBoolean(defaultExcludes);
boolean doCaseSensitive = toPrimitiveBoolean(caseSensitive);
addPublicPackage(modulePackageAsPath, path, includeList, excludeList,
skipList, doDefaultExcludes, doCaseSensitive);
}
protected Schema __replace_with_begin(String className) {
RuleReplaceWith rule = new RuleReplaceWith(className);
moduleDef.getRules().prepend(rule);
return new FullConditionSchema(rule.getRootCondition());
}
/**
* @param src a partial or full url to a script file to inject
* @return <code>null</code> since there can be no children
*/
@SuppressWarnings("unused")
protected Schema __script_begin(String src) {
return fChild = new ScriptSchema();
}
protected void __script_end(String src) {
ScriptSchema childSchema = (ScriptSchema) fChild;
String js = childSchema.getScript();
if (js != null) {
logger.log(
TreeLogger.WARN,
"Injected scripts no longer use an associated JavaScript block; ignoring.",
null);
}
moduleDef.getScripts().append(new Script(src));
}
protected Schema __servlet_begin(String path, String servletClass)
throws UnableToCompleteException {
// Only absolute paths, although it is okay to have multiple slashes.
if (!path.startsWith("/")) {
logger.log(TreeLogger.ERROR, "Servlet path '" + path
+ "' must begin with forward slash (e.g. '/foo')", null);
throw new UnableToCompleteException();
}
// Map the path within this module.
moduleDef.mapServlet(path, servletClass);
return null;
}
protected Schema __set_configuration_property_begin(PropertyName name,
String value) throws UnableToCompleteException {
Property existingProperty = moduleDef.getProperties().find(name.token);
if (existingProperty == null) {
// If a property is created by "set-configuration-property" without
// a previous "define-configuration-property", allow it for backwards
// compatibility but don't allow multiple values.
existingProperty = moduleDef.getProperties().createConfiguration(
name.token, false);
if (!propertySettings.containsKey(name.token)) {
propertySettings.put(name.token, moduleName);
}
logger.log(TreeLogger.WARN, "Setting configuration property named "
+ name.token + " in " + moduleName
+ " that has not been previously defined."
+ " This may be disallowed in the future.");
} else if (!(existingProperty instanceof ConfigurationProperty)) {
if (existingProperty instanceof BindingProperty) {
logger.log(TreeLogger.ERROR, "The property " + name.token
+ " is already defined as a deferred-binding property");
} else {
// Future proofing if other subclasses are added.
logger.log(TreeLogger.ERROR, "May not replace property named "
+ name.token + " of unknown type "
+ existingProperty.getClass().getName());
}
throw new UnableToCompleteException();
}
((ConfigurationProperty) existingProperty).setValue(value);
// No children.
return null;
}
@SuppressWarnings("unused")
protected Schema __set_property_begin(BindingProperty prop,
PropertyValue[] value) throws UnableToCompleteException {
bindingPropertyCondition = new ConditionAll();
return new PropertyConditionSchema(bindingPropertyCondition);
}
protected void __set_property_end(BindingProperty prop,
PropertyValue[] value) throws UnableToCompleteException {
boolean error = false;
String[] stringValues = new String[value.length];
for (int i = 0, len = stringValues.length; i < len; i++) {
if (!prop.isDefinedValue(stringValues[i] = value[i].token)) {
logger.log(TreeLogger.ERROR, "The value " + stringValues[i]
+ " was not previously defined.");
error = true;
}
}
if (error) {
throw new UnableToCompleteException();
}
// No conditions were specified, so use the property's root condition
if (!bindingPropertyCondition.getConditions().iterator().hasNext()) {
bindingPropertyCondition = prop.getRootCondition();
}
prop.setAllowedValues(bindingPropertyCondition, stringValues);
}
protected Schema __set_property_fallback_begin(BindingProperty prop,
PropertyValue value) throws UnableToCompleteException {
boolean error = true;
for (String possibleValue : prop.getAllowedValues(prop.getRootCondition())) {
if (possibleValue.equals(value.token)) {
error = false;
break;
}
}
if (error) {
logger.log(TreeLogger.ERROR, "The fallback value '" + value.token
+ "' was not previously defined for property '" + prop.getName()
+ "'");
throw new UnableToCompleteException();
}
prop.setFallback(value.token);
return null;
}
/**
* Indicates which subdirectories contain translatable source without
* necessarily adding a sourcepath entry.
*/
@SuppressWarnings("unused")
protected Schema __source_begin(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
return fChild = new IncludeExcludeSchema();
}
protected void __source_end(String path, String includes, String excludes,
String skips, String defaultExcludes, String caseSensitive) {
addSourcePackage(path, includes, excludes, skips, defaultExcludes,
caseSensitive, false);
}
/**
* @param src a partial or full url to a stylesheet file to inject
* @return <code>null</code> since there can be no children
*/
protected Schema __stylesheet_begin(String src) {
moduleDef.getStyles().append(src);
return null;
}
/**
* Like adding a translatable source package, but such that it uses the
* module's package itself as its sourcepath root entry.
*/
@SuppressWarnings("unused")
protected Schema __super_source_begin(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
return fChild = new IncludeExcludeSchema();
}
protected void __super_source_end(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
addSourcePackage(path, includes, excludes, skips, defaultExcludes,
caseSensitive, true);
}
private void addDelimitedStringToSet(String delimited, String delimiter,
Set<String> toSet) {
if (delimited.length() > 0) {
String[] split = delimited.split(delimiter);
for (int i = 0; i < split.length; ++i) {
if (split[i].length() > 0) {
toSet.add(split[i]);
}
}
}
}
private void addPublicPackage(String parentDir, String relDir,
String[] includeList, String[] excludeList, String[] skipList,
boolean defaultExcludes, boolean caseSensitive) {
String normChildDir = normalizePathEntry(relDir);
if (normChildDir.startsWith("/")) {
logger.log(TreeLogger.WARN, "Non-relative public package: "
+ normChildDir, null);
return;
}
if (normChildDir.startsWith("./") || normChildDir.indexOf("/./") >= 0) {
logger.log(TreeLogger.WARN, "Non-canonical public package: "
+ normChildDir, null);
return;
}
if (normChildDir.startsWith("../") || normChildDir.indexOf("/../") >= 0) {
logger.log(TreeLogger.WARN, "Non-canonical public package: "
+ normChildDir, null);
return;
}
String fullDir = parentDir + normChildDir;
moduleDef.addPublicPackage(fullDir, includeList, excludeList, skipList,
defaultExcludes, caseSensitive);
}
private void addSourcePackage(String relDir, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive, boolean isSuperSource) {
IncludeExcludeSchema childSchema = ((IncludeExcludeSchema) fChild);
foundExplicitSourceOrSuperSource = true;
Set<String> includeSet = childSchema.getIncludes();
addDelimitedStringToSet(includes, "[ ,]", includeSet);
String[] includeList = includeSet.toArray(new String[includeSet.size()]);
Set<String> excludeSet = childSchema.getExcludes();
addDelimitedStringToSet(excludes, "[ ,]", excludeSet);
String[] excludeList = excludeSet.toArray(new String[excludeSet.size()]);
Set<String> skipSet = childSchema.getSkips();
addDelimitedStringToSet(skips, "[ ,]", skipSet);
String[] skipList = skipSet.toArray(new String[skipSet.size()]);
boolean doDefaultExcludes = toPrimitiveBoolean(defaultExcludes);
boolean doCaseSensitive = toPrimitiveBoolean(caseSensitive);
addSourcePackage(modulePackageAsPath, relDir, includeList, excludeList,
skipList, doDefaultExcludes, doCaseSensitive, isSuperSource);
}
private void addSourcePackage(String modulePackagePath, String relDir,
String[] includeList, String[] excludeList, String[] skipList,
boolean defaultExcludes, boolean caseSensitive, boolean isSuperSource) {
String normChildDir = normalizePathEntry(relDir);
if (normChildDir.startsWith("/")) {
logger.log(TreeLogger.WARN, "Non-relative source package: "
+ normChildDir, null);
return;
}
if (normChildDir.startsWith("./") || normChildDir.indexOf("/./") >= 0) {
logger.log(TreeLogger.WARN, "Non-canonical source package: "
+ normChildDir, null);
return;
}
if (normChildDir.startsWith("../") || normChildDir.indexOf("/../") >= 0) {
logger.log(TreeLogger.WARN, "Non-canonical source package: "
+ normChildDir, null);
return;
}
String fullPackagePath = modulePackagePath + normChildDir;
if (isSuperSource) {
/*
* Super source does not include the module package path as part of the
* logical class names.
*/
moduleDef.addSuperSourcePackage(fullPackagePath, includeList,
excludeList, skipList, defaultExcludes, caseSensitive);
} else {
/*
* Add the full package path to the include/exclude/skip lists since the
* logical name of classes on the source path includes the package path
* but the include, exclude, and skip lists do not.
*/
addPrefix(includeList, fullPackagePath);
addPrefix(excludeList, fullPackagePath);
addPrefix(skipList, fullPackagePath);
moduleDef.addSourcePackage(fullPackagePath, includeList, excludeList,
skipList, defaultExcludes, caseSensitive);
}
}
/**
* Normalizes a path entry such that it does not start with but does end
* with '/'.
*/
private String normalizePathEntry(String path) {
path = path.trim();
if (path.length() == 0) {
return "";
}
path = path.replace('\\', '/');
if (!path.endsWith("/")) {
path += "/";
}
return path;
}
}
/**
* Processes attributes of java.lang.Class type.
*/
private final class ClassAttrCvt extends AttributeConverter {
@Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
try {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return cl.loadClass(value);
} catch (ClassNotFoundException e) {
Messages.UNABLE_TO_LOAD_CLASS.log(logger, value, e);
throw new UnableToCompleteException();
}
}
}
/**
* All conditional expressions, including those based on types.
*/
private final class FullConditionSchema extends PropertyConditionSchema {
protected final String __when_type_assignable_1_class = null;
protected final String __when_type_is_1_class = null;
public FullConditionSchema(CompoundCondition parentCondition) {
super(parentCondition);
}
protected Schema __when_type_assignable_begin(String className) {
Condition cond = new ConditionWhenTypeAssignableTo(className);
parentCondition.getConditions().add(cond);
// No children allowed.
return null;
}
protected Schema __when_type_is_begin(String className) {
Condition cond = new ConditionWhenTypeIs(className);
parentCondition.getConditions().add(cond);
// No children allowed.
return null;
}
@Override
protected Schema subSchema(CompoundCondition cond) {
return new FullConditionSchema(cond);
}
}
private static final class IncludeExcludeSchema extends Schema {
protected final String __exclude_1_name = null;
protected final String __include_1_name = null;
protected final String __skip_1_name = null;
/**
* Files neither in this module, nor available from other modules.
*/
private final Set<String> excludes = new HashSet<String>();
/**
* Files in this module.
*/
private final Set<String> includes = new HashSet<String>();
/**
* Files not in this module, but available from other modules.
*/
private final Set<String> skips = new HashSet<String>();
public Set<String> getExcludes() {
return excludes;
}
public Set<String> getIncludes() {
return includes;
}
public Set<String> getSkips() {
return skips;
}
protected Schema __exclude_begin(String name) {
excludes.add(name);
return null;
}
protected Schema __include_begin(String name) {
includes.add(name);
return null;
}
protected Schema __skip_begin(String name) {
skips.add(name);
return null;
}
}
private static class LinkerName {
public final String name;
public LinkerName(String name) {
this.name = name;
}
}
/**
* Converts a string into a linker name, validating it in the process.
*/
private final class LinkerNameAttrCvt extends AttributeConverter {
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
// Ensure the value is a valid Java identifier
if (!Util.isValidJavaIdent(value)) {
Messages.LINKER_NAME_INVALID.log(logger, value, null);
throw new UnableToCompleteException();
}
// It is a valid name.
return new LinkerName(value);
}
}
/**
* A dotted Java identifier or null. Zero-length names are represented as
* null.
*/
private static class NullableName {
public final String token;
public NullableName(String token) {
this.token = token;
}
}
/**
* Converts a string into a nullable name, validating it in the process.
*/
private final class NullableNameAttrCvt extends AttributeConverter {
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
if (value == null || value.length() == 0) {
return new NullableName(null);
}
// Ensure each part of the name is valid.
//
String[] tokens = (value + ". ").split("\\.");
for (int i = 0; i < tokens.length - 1; i++) {
String token = tokens[i];
if (!Util.isValidJavaIdent(token)) {
Messages.NAME_INVALID.log(logger, value, null);
throw new UnableToCompleteException();
}
}
// It is a valid name.
//
return new NullableName(value);
}
}
/**
* Converts property names into their corresponding property objects.
*/
private final class PropertyAttrCvt extends AttributeConverter {
private Class<? extends Property> concreteType;
public PropertyAttrCvt(Class<? extends Property> concreteType) {
this.concreteType = concreteType;
}
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
// Find the named property.
//
Property prop = moduleDef.getProperties().find(value);
if (prop != null) {
// Found it.
//
if (concreteType.isInstance(prop)) {
return prop;
}
logger.log(TreeLogger.ERROR, "The specified property '"
+ prop.getName() + "' is not of the correct type; found '"
+ prop.getClass().getSimpleName() + "' expecting '"
+ concreteType.getSimpleName() + "'");
} else {
// Property not defined. This is a problem.
//
Messages.PROPERTY_NOT_FOUND.log(logger, value, null);
}
throw new UnableToCompleteException();
}
}
/**
* A limited number of conditional predicates based only on properties.
*/
private class PropertyConditionSchema extends Schema {
protected final String __when_linker_added_1_name = null;
protected final String __when_property_is_1_name = null;
protected final String __when_property_is_2_value = null;
protected final CompoundCondition parentCondition;
public PropertyConditionSchema(CompoundCondition parentCondition) {
this.parentCondition = parentCondition;
}
protected Schema __all_begin() {
CompoundCondition cond = new ConditionAll();
parentCondition.getConditions().add(cond);
return subSchema(cond);
}
protected Schema __any_begin() {
CompoundCondition cond = new ConditionAny();
parentCondition.getConditions().add(cond);
return subSchema(cond);
}
protected Schema __none_begin() {
CompoundCondition cond = new ConditionNone();
parentCondition.getConditions().add(cond);
return subSchema(cond);
}
protected Schema __when_linker_added_begin(LinkerName linkerName) {
Condition cond = new ConditionWhenLinkerAdded(linkerName.name);
parentCondition.getConditions().add(cond);
// No children allowed.
return null;
}
/*
* We intentionally use the BindingProperty type here for tough-love on
* module writers. It prevents them from trying to create property providers
* for unknown properties.
*/
protected Schema __when_property_is_begin(BindingProperty prop,
PropertyValue value) {
Condition cond = new ConditionWhenPropertyIs(prop.getName(), value.token);
parentCondition.getConditions().add(cond);
// No children allowed.
return null;
}
protected Schema subSchema(CompoundCondition cond) {
return new PropertyConditionSchema(cond);
}
}
private static class PropertyName {
public final String token;
public PropertyName(String token) {
this.token = token;
}
}
/**
* Converts a string into a property name, validating it in the process.
*/
private final class PropertyNameAttrCvt extends AttributeConverter {
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
// Ensure each part of the name is valid.
//
String[] tokens = (value + ". ").split("\\.");
for (int i = 0; i < tokens.length - 1; i++) {
String token = tokens[i];
if (!Util.isValidJavaIdent(token)) {
Messages.PROPERTY_NAME_INVALID.log(logger, value, null);
throw new UnableToCompleteException();
}
}
// It is a valid name.
//
return new PropertyName(value);
}
}
private static class PropertyValue {
public final String token;
public PropertyValue(String token) {
this.token = token;
}
}
/**
* Converts a comma-separated string into an array of property value tokens.
*/
private final class PropertyValueArrayAttrCvt extends AttributeConverter {
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
String[] tokens = value.split(",");
PropertyValue[] values = new PropertyValue[tokens.length];
// Validate each token as we copy it over.
//
for (int i = 0; i < tokens.length; i++) {
values[i] = (PropertyValue) propValueAttrCvt.convertToArg(schema, line,
elem, attr, tokens[i]);
}
return values;
}
}
/**
* Converts a string into a property value, validating it in the process.
*/
private final class PropertyValueAttrCvt extends AttributeConverter {
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
String token = value.trim();
if (Util.isValidJavaIdent(token)) {
return new PropertyValue(token);
} else {
Messages.PROPERTY_VALUE_INVALID.log(logger, token, null);
throw new UnableToCompleteException();
}
}
}
private static class PropertyValueGlob {
public final String token;
public PropertyValueGlob(String token) {
this.token = token;
}
public boolean isGlob() {
return token.contains(BindingProperty.GLOB_STAR);
}
}
/**
* Converts a comma-separated string into an array of property value tokens.
*/
private final class PropertyValueGlobArrayAttrCvt extends AttributeConverter {
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
String[] tokens = value.split(",");
PropertyValueGlob[] values = new PropertyValueGlob[tokens.length];
// Validate each token as we copy it over.
//
for (int i = 0; i < tokens.length; i++) {
values[i] = (PropertyValueGlob) propValueGlobAttrCvt.convertToArg(
schema, line, elem, attr, tokens[i]);
}
return values;
}
}
/**
* Converts a string into a property value glob, validating it in the process.
*/
private final class PropertyValueGlobAttrCvt extends AttributeConverter {
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
String token = value.trim();
String tokenNoStar = token.replaceAll(
Pattern.quote(BindingProperty.GLOB_STAR), "");
if (BindingProperty.GLOB_STAR.equals(token)
|| Util.isValidJavaIdent(tokenNoStar)) {
return new PropertyValueGlob(token);
} else {
Messages.PROPERTY_VALUE_INVALID.log(logger, token, null);
throw new UnableToCompleteException();
}
}
}
private static class ScriptSchema extends Schema {
private StringBuffer script;
private int startLineNumber = -1;
public ScriptSchema() {
}
public void __text(String text) {
if (script == null) {
script = new StringBuffer();
startLineNumber = getLineNumber();
}
script.append(text);
}
public String getScript() {
return script != null ? script.toString() : null;
}
public int getStartLineNumber() {
return startLineNumber;
}
}
/**
* Map of property names to the modules that defined them explicitly using
* <define-configuration-property>, used to generate warning messages.
*/
private static final HashMap<String, String> propertyDefinitions = new HashMap<String, String>();
/**
* Map of property names to the modules that defined them implicitly using
* <set-configuration-property>, used to generate warning messages.
*/
private static final HashMap<String, String> propertySettings = new HashMap<String, String>();
private static void addPrefix(String[] strings, String prefix) {
for (int i = 0; i < strings.length; ++i) {
strings[i] = prefix + strings[i];
}
}
/**
* Returns <code>true</code> if the string equals "true" or "yes" using a case
* insensitive comparison.
*/
private static boolean toPrimitiveBoolean(String s) {
return "yes".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s);
}
protected final String __module_1_rename_to = "";
private final PropertyAttrCvt bindingPropAttrCvt = new PropertyAttrCvt(
BindingProperty.class);
private final BodySchema bodySchema;
private final ClassAttrCvt classAttrCvt = new ClassAttrCvt();
private final PropertyAttrCvt configurationPropAttrCvt = new PropertyAttrCvt(
ConfigurationProperty.class);
private boolean foundAnyPublic;
private boolean foundExplicitSourceOrSuperSource;
private final JsProgram jsPgm = new JsProgram();
private final LinkerNameAttrCvt linkerNameAttrCvt = new LinkerNameAttrCvt();
private final ModuleDefLoader loader;
private final TreeLogger logger;
private final ModuleDef moduleDef;
private final String moduleName;
private final String modulePackageAsPath;
private final URL moduleURL;
private final NullableNameAttrCvt nullableNameAttrCvt = new NullableNameAttrCvt();
private final PropertyNameAttrCvt propNameAttrCvt = new PropertyNameAttrCvt();
private final PropertyValueArrayAttrCvt propValueArrayAttrCvt = new PropertyValueArrayAttrCvt();
private final PropertyValueAttrCvt propValueAttrCvt = new PropertyValueAttrCvt();
private final PropertyValueGlobArrayAttrCvt propValueGlobArrayAttrCvt = new PropertyValueGlobArrayAttrCvt();
private final PropertyValueGlobAttrCvt propValueGlobAttrCvt = new PropertyValueGlobAttrCvt();
public ModuleDefSchema(TreeLogger logger, ModuleDefLoader loader,
String moduleName, URL moduleURL, String modulePackageAsPath,
ModuleDef toConfigure) {
this.logger = logger;
this.loader = loader;
this.moduleName = moduleName;
this.moduleURL = moduleURL;
this.modulePackageAsPath = modulePackageAsPath;
assert (modulePackageAsPath.endsWith("/") || modulePackageAsPath.equals(""));
this.moduleDef = toConfigure;
this.bodySchema = new BodySchema();
registerAttributeConverter(PropertyName.class, propNameAttrCvt);
registerAttributeConverter(BindingProperty.class, bindingPropAttrCvt);
registerAttributeConverter(ConfigurationProperty.class,
configurationPropAttrCvt);
registerAttributeConverter(PropertyValue.class, propValueAttrCvt);
registerAttributeConverter(PropertyValue[].class, propValueArrayAttrCvt);
registerAttributeConverter(PropertyValueGlob.class, propValueGlobAttrCvt);
registerAttributeConverter(PropertyValueGlob[].class,
propValueGlobArrayAttrCvt);
registerAttributeConverter(LinkerName.class, linkerNameAttrCvt);
registerAttributeConverter(NullableName.class, nullableNameAttrCvt);
registerAttributeConverter(Class.class, classAttrCvt);
}
@SuppressWarnings("unused")
protected Schema __module_begin(NullableName renameTo) {
return bodySchema;
}
protected void __module_end(NullableName renameTo) {
// Maybe infer source and public.
//
if (!foundExplicitSourceOrSuperSource) {
bodySchema.addSourcePackage(modulePackageAsPath, "client", Empty.STRINGS,
Empty.STRINGS, Empty.STRINGS, true, true, false);
}
if (!foundAnyPublic) {
bodySchema.addPublicPackage(modulePackageAsPath, "public", Empty.STRINGS,
Empty.STRINGS, Empty.STRINGS, true, true);
}
// We do this in __module_end so this value is never inherited
moduleDef.setNameOverride(renameTo.token);
}
/**
* Parses handwritten JavaScript found in the module xml, logging an error
* message and throwing an exception if there's a problem.
*
* @param startLineNumber the start line number where the script was found;
* used to report errors
* @param script the JavaScript to wrap in "function() { script }" to parse
* @return the parsed function
* @throws UnableToCompleteException
*/
private JsFunction parseJsBlock(int startLineNumber, String script)
throws UnableToCompleteException {
script = "function() { " + script + "}";
StringReader r = new StringReader(script);
List<JsStatement> stmts;
try {
// TODO Provide more context here
stmts = JsParser.parse(jsPgm.createSourceInfo(startLineNumber,
moduleURL.toExternalForm()), jsPgm.getScope(), r);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Error reading script source", e);
throw new UnableToCompleteException();
} catch (JsParserException e) {
logger.log(TreeLogger.ERROR, "Error parsing JavaScript source", e);
throw new UnableToCompleteException();
}
// Rip the body out of the parsed function and attach the JavaScript
// AST to the method.
//
JsFunction fn = (JsFunction) ((JsExprStmt) stmts.get(0)).getExpression();
return fn;
}
}
// CHECKSTYLE_NAMING_ON