aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax
diff options
context:
space:
mode:
authorGravatar Lukacs Berki <lberki@google.com>2015-09-18 11:40:12 +0000
committerGravatar Damien Martin-Guillerez <dmarting@google.com>2015-09-21 08:56:51 +0000
commitffa73addaf80ab7a9cf9f98e5d4414b152ae6cb6 (patch)
treef498621bb8634d934815dbaddf90400eda0ce28d /src/main/java/com/google/devtools/build/lib/syntax
parent29ad862a5bd45d2fdbb921f36bbb95816cf47acc (diff)
Separate build-specific types and types inherent to Skylark.
-- MOS_MIGRATED_REVID=103374106
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FilesetEntry.java204
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SelectorList.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SelectorValue.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Type.java626
9 files changed, 640 insertions, 211 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
index 43c25aa2e6..1fb8747374 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -21,8 +21,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.packages.Type.ConversionException;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
+import com.google.devtools.build.lib.syntax.Type.ConversionException;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
index cf98c53b47..e6974540a4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
@@ -278,7 +278,8 @@ public final class BinaryOperatorExpression extends Expression {
}
if (lval instanceof SelectorValue || rval instanceof SelectorValue
- || lval instanceof SelectorList || rval instanceof SelectorList) {
+ || lval instanceof SelectorList
+ || rval instanceof SelectorList) {
return SelectorList.concat(getLocation(), lval, rval);
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index eb26e38bb3..7ee555cf2a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -114,7 +114,7 @@ public final class EvalUtils {
/**
* Checks that an Object is a valid key for a Skylark dict.
* @param o an Object to validate
- * @throws an EvalException if o is not a valid key
+ * @throws EvalException if o is not a valid key
*/
static void checkValidDictKey(Object o) throws EvalException {
// TODO(bazel-team): check that all recursive elements are both Immutable AND Comparable.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FilesetEntry.java b/src/main/java/com/google/devtools/build/lib/syntax/FilesetEntry.java
deleted file mode 100644
index c876d6328f..0000000000
--- a/src/main/java/com/google/devtools/build/lib/syntax/FilesetEntry.java
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright 2014 Google Inc. All rights reserved.
-//
-// 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.devtools.build.lib.syntax;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.lib.vfs.PathFragment;
-
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-/**
- * FilesetEntry is a value object used to represent a "FilesetEntry" inside a "Fileset" BUILD rule.
- */
-@SkylarkModule(
- name = "FilesetEntry",
- doc = "",
- documented = false)
-public final class FilesetEntry implements SkylarkValue {
-
- @Override
- public boolean isImmutable() {
- return false;
- }
-
- @Override
- public void write(Appendable buffer, char quotationMark) {
- Printer.append(buffer, "FilesetEntry(srcdir = ");
- Printer.write(buffer, getSrcLabel().toString(), quotationMark);
- Printer.append(buffer, ", files = ");
- Printer.write(buffer, Printer.makeStringList(getFiles()), quotationMark);
- Printer.append(buffer, ", excludes = ");
- Printer.write(buffer, Printer.makeList(getExcludes()), quotationMark);
- Printer.append(buffer, ", destdir = ");
- Printer.write(buffer, getDestDir().getPathString(), quotationMark);
- Printer.append(buffer, ", strip_prefix = ");
- Printer.write(buffer, getStripPrefix(), quotationMark);
- Printer.append(buffer, ", symlinks = ");
- Printer.append(buffer, quotationMark);
- Printer.append(buffer, getSymlinkBehavior().toString());
- Printer.append(buffer, quotationMark);
- Printer.append(buffer, ")");
- }
-
- /** SymlinkBehavior decides what to do when a source file of a FilesetEntry is a symlink. */
- public enum SymlinkBehavior {
- /** Just copies the symlink as-is. May result in dangling links. */
- COPY,
- /** Follow the link and make the destination point to the absolute path of the final target. */
- DEREFERENCE;
-
- public static SymlinkBehavior parse(String value) throws IllegalArgumentException {
- return valueOf(value.toUpperCase());
- }
-
- @Override
- public String toString() {
- return super.toString().toLowerCase();
- }
- }
-
- private final Label srcLabel;
- @Nullable private final ImmutableList<Label> files;
- @Nullable private final ImmutableSet<String> excludes;
- private final PathFragment destDir;
- private final SymlinkBehavior symlinkBehavior;
- private final String stripPrefix;
-
- /**
- * Constructs a FilesetEntry with the given values.
- *
- * @param srcLabel the label of the source directory. Must be non-null.
- * @param files The explicit files to include. May be null.
- * @param excludes The files to exclude. Man be null. May only be non-null if files is null.
- * @param destDir The target-relative output directory.
- * @param symlinkBehavior how to treat symlinks on the input. See
- * {@link FilesetEntry.SymlinkBehavior}.
- * @param stripPrefix the prefix to strip from the package-relative path. If ".", keep only the
- * basename.
- */
- public FilesetEntry(Label srcLabel,
- @Nullable List<Label> files,
- @Nullable List<String> excludes,
- String destDir,
- SymlinkBehavior symlinkBehavior,
- String stripPrefix) {
- this.srcLabel = checkNotNull(srcLabel);
- this.destDir = new PathFragment((destDir == null) ? "" : destDir);
- this.files = files == null ? null : ImmutableList.copyOf(files);
- this.excludes = (excludes == null || excludes.isEmpty()) ? null : ImmutableSet.copyOf(excludes);
- this.symlinkBehavior = symlinkBehavior;
- this.stripPrefix = stripPrefix;
- }
-
- /**
- * @return the source label.
- */
- public Label getSrcLabel() {
- return srcLabel;
- }
-
- /**
- * @return the destDir. Non null.
- */
- public PathFragment getDestDir() {
- return destDir;
- }
-
- /**
- * @return how symlinks should be handled.
- */
- public SymlinkBehavior getSymlinkBehavior() {
- return symlinkBehavior;
- }
-
- /**
- * @return an immutable list of excludes. Null if none specified.
- */
- @Nullable
- public ImmutableSet<String> getExcludes() {
- return excludes;
- }
-
- /**
- * @return an immutable list of file labels. Null if none specified.
- */
- @Nullable
- public ImmutableList<Label> getFiles() {
- return files;
- }
-
- /**
- * @return true if this Fileset should get files from the source directory.
- */
- public boolean isSourceFileset() {
- return "BUILD".equals(srcLabel.getName());
- }
-
- /**
- * @return all prerequisite labels in the FilesetEntry.
- */
- public Collection<Label> getLabels() {
- Set<Label> labels = new LinkedHashSet<>();
- if (files != null) {
- labels.addAll(files);
- } else {
- labels.add(srcLabel);
- }
- return labels;
- }
-
- /**
- * @return the prefix that should be stripped from package-relative path names.
- */
- public String getStripPrefix() {
- return stripPrefix;
- }
-
- /**
- * @return null if the entry is valid, and a human-readable error message otherwise.
- */
- @Nullable
- public String validate() {
- if (excludes != null && files != null) {
- return "Cannot specify both 'files' and 'excludes' in a FilesetEntry";
- } else if (files != null && !isSourceFileset()) {
- return "Cannot specify files with Fileset label '" + srcLabel + "'";
- } else if (destDir.isAbsolute()) {
- return "Cannot specify absolute destdir '" + destDir + "'";
- } else if (!stripPrefix.equals(".") && files == null) {
- return "If the strip prefix is not '.', files must be specified";
- } else if (new PathFragment(stripPrefix).containsUplevelReferences()) {
- return "Strip prefix must not contain uplevel references";
- } else {
- return null;
- }
- }
-
- @Override
- public String toString() {
- return String.format("FilesetEntry(srcdir=%s, destdir=%s, strip_prefix=%s, symlinks=%s, "
- + "%d file(s) and %d excluded)", srcLabel, destDir, stripPrefix, symlinkBehavior,
- files != null ? files.size() : 0,
- excludes != null ? excludes.size() : 0);
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index a6ef07d4c8..2afcbc3017 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -22,13 +22,12 @@ import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.packages.Type;
-import com.google.devtools.build.lib.packages.Type.ConversionException;
import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList;
+import com.google.devtools.build.lib.syntax.Type.ConversionException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1322,7 +1321,8 @@ public class MethodLibrary {
@Param(name = "x", type = Map.class, doc = "The parameter to convert.")})
private static final BuiltinFunction select = new BuiltinFunction("select") {
public Object invoke(Map<?, ?> dict) throws EvalException {
- return SelectorList.of(new SelectorValue(dict));
+ return SelectorList
+ .of(new SelectorValue(dict));
}
};
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SelectorList.java b/src/main/java/com/google/devtools/build/lib/syntax/SelectorList.java
index f77f8d7fe9..04c5142059 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SelectorList.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SelectorList.java
@@ -38,6 +38,9 @@ import java.util.List;
* </pre>
*/
public final class SelectorList {
+ // TODO(build-team): Selectors are currently split between .packages and .syntax . They should
+ // really all be in .packages, but then we'd need to figure out a way how to extend binary
+ // operators, which is a non-trivial problem.
private final Class<?> type;
private final List<Object> elements;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SelectorValue.java b/src/main/java/com/google/devtools/build/lib/syntax/SelectorValue.java
index 5cdbb0b16b..ccf1f7def5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SelectorValue.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SelectorValue.java
@@ -30,6 +30,9 @@ import java.util.Map;
* </pre>
*/
public final class SelectorValue {
+ // TODO(build-team): Selectors are currently split between .packages and .syntax . They should
+ // really all be in .packages, but then we'd need to figure out a way how to extend binary
+ // operators, which is a non-trivial problem.
private final Map<?, ?> dictionary;
private final Class<?> type;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
index 21db288515..1727db21ff 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
@@ -659,7 +659,7 @@ public abstract class SkylarkType implements Serializable {
return ImmutableList.of();
}
List<TYPE> results = new ArrayList<>();
- for (Object object : com.google.devtools.build.lib.packages.Type.LIST.convert(obj, what)) {
+ for (Object object : com.google.devtools.build.lib.syntax.Type.LIST.convert(obj, what)) {
try {
results.add(type.cast(object));
} catch (ClassCastException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Type.java b/src/main/java/com/google/devtools/build/lib/syntax/Type.java
new file mode 100644
index 0000000000..825403e057
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Type.java
@@ -0,0 +1,626 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// 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.devtools.build.lib.syntax;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.util.LoggingUtil;
+import com.google.devtools.build.lib.util.StringCanonicalizer;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.logging.Level;
+
+import javax.annotation.Nullable;
+
+/**
+ * <p>Root of Type symbol hierarchy for values in the build language.</p>
+ *
+ * <p>Type symbols are primarily used for their <code>convert</code> method,
+ * which is a kind of cast operator enabling conversion from untyped (Object)
+ * references to values in the build language, to typed references.</p>
+ *
+ * <p>For example, this code type-converts a value <code>x</code> returned by
+ * the evaluator, to a list of strings:</p>
+ *
+ * <pre>
+ * Object x = expr.eval(env);
+ * List&lt;String&gt; s = Type.STRING_LIST.convert(x);
+ * </pre>
+ */
+public abstract class Type<T> {
+
+ protected Type() {}
+
+ /**
+ * Converts untyped Object x resulting from the evaluation of an expression in the build language,
+ * into a typed object of type T.
+ *
+ * <p>x must be *directly* convertible to this type. This therefore disqualifies "selector
+ * expressions" of the form "{ config1: 'value1_of_orig_type', config2: 'value2_of_orig_type; }"
+ * (which support configurable attributes). To handle those expressions, see
+ * {@link com.google.devtools.build.lib.packages.BuildType#selectableConvert}.
+ *
+ * @param x the build-interpreter value to convert.
+ * @param what a string description of what x is for; should be included in
+ * any exception thrown. Grammatically, must describe a syntactic
+ * construct, e.g. "attribute 'srcs' of rule foo".
+ * @param context the label of the current BUILD rule; must be non-null if resolution of
+ * package-relative label strings is required
+ * @throws ConversionException if there was a problem performing the type conversion
+ */
+ public abstract T convert(Object x, String what, @Nullable Object context)
+ throws ConversionException;
+ // TODO(bazel-team): Check external calls (e.g. in PackageFactory), verify they always want
+ // this over selectableConvert.
+
+ /**
+ * Equivalent to {@link #convert(Object, String, Object)} where the label is {@code null}.
+ * Useful for converting values to types that do not involve the type {@code LABEL}
+ * and hence do not require the label of the current package.
+ */
+ public final T convert(Object x, String what) throws ConversionException {
+ return convert(x, what, null);
+ }
+
+ /**
+ * Like {@link #convert(Object, String, Object)}, but converts skylark {@code None}
+ * to given {@code defaultValue}.
+ */
+ @Nullable public final T convertOptional(Object x,
+ String what, @Nullable Object context, T defaultValue)
+ throws ConversionException {
+ if (EvalUtils.isNullOrNone(x)) {
+ return defaultValue;
+ }
+ return convert(x, what, context);
+ }
+
+ /**
+ * Like {@link #convert(Object, String, Object)}, but converts skylark {@code None}
+ * to java {@code null}.
+ */
+ @Nullable public final T convertOptional(Object x, String what, @Nullable Object context)
+ throws ConversionException {
+ return convertOptional(x, what, context, null);
+ }
+
+ /**
+ * Like {@link #convert(Object, String)}, but converts skylark {@code NONE} to java {@code null}.
+ */
+ @Nullable public final T convertOptional(Object x, String what) throws ConversionException {
+ return convertOptional(x, what, null);
+ }
+
+ public abstract T cast(Object value);
+
+ @Override
+ public abstract String toString();
+
+ /**
+ * Returns the default value for this type; may return null iff no default is defined for this
+ * type.
+ */
+ public abstract T getDefaultValue();
+
+ /**
+ * Flatten the an instance of the type if the type is a composite one.
+ *
+ * <p>This is used to support reliable label visitation in
+ * {@link com.google.devtools.build.lib.packages.AbstractAttributeMapper#visitLabels}. To preserve
+ * that reliability, every type should faithfully define its own instance of this method. In other
+ * words, be careful about defining default instances in base types that get auto-inherited by
+ * their children. Keep all definitions as explicit as possible.
+ */
+ public abstract Collection<? extends Object> flatten(Object value);
+
+ /**
+ * {@link #flatten} return value for types that don't contain labels.
+ */
+ protected static final Collection<Object> NOT_COMPOSITE_TYPE = ImmutableList.of();
+
+ /**
+ * Implementation of concatenation for this type (e.g. "val1 + val2"). Returns null to
+ * designate concatenation isn't supported.
+ */
+ public T concat(Iterable<T> elements) {
+ return null;
+ }
+
+ /**
+ * Converts an initialized Type object into a tag set representation.
+ * This operation is only valid for certain sub-Types which are guaranteed
+ * to be properly initialized.
+ *
+ * @param value the actual value
+ * @throws UnsupportedOperationException if the concrete type does not support
+ * tag conversion or if a convertible type has no initialized value.
+ */
+ public Set<String> toTagSet(Object value, String name) {
+ String msg = "Attribute " + name + " does not support tag conversion.";
+ throw new UnsupportedOperationException(msg);
+ }
+
+ /**
+ * The type of an integer.
+ */
+ public static final Type<Integer> INTEGER = new IntegerType();
+
+ /**
+ * The type of a string.
+ */
+ public static final Type<String> STRING = new StringType();
+
+ /**
+ * The type of a boolean.
+ */
+ public static final Type<Boolean> BOOLEAN = new BooleanType();
+
+ /**
+ * The type of a list of not-yet-typed objects.
+ */
+ public static final ObjectListType OBJECT_LIST = new ObjectListType();
+
+ /**
+ * The type of a list of {@linkplain #STRING strings}.
+ */
+ public static final ListType<String> STRING_LIST = ListType.create(STRING);
+
+ /**
+ * The type of a list of {@linkplain #INTEGER strings}.
+ */
+ public static final ListType<Integer> INTEGER_LIST = ListType.create(INTEGER);
+
+ /**
+ * The type of a dictionary of {@linkplain #STRING strings}.
+ */
+ public static final DictType<String, String> STRING_DICT = DictType.create(STRING, STRING);
+
+ /**
+ * The type of a dictionary of {@linkplain #STRING_LIST label lists}.
+ */
+ public static final DictType<String, List<String>> STRING_LIST_DICT =
+ DictType.create(STRING, STRING_LIST);
+
+ /**
+ * The type of a dictionary of {@linkplain #STRING strings}, where each entry
+ * maps to a single string value.
+ */
+ public static final DictType<String, String> STRING_DICT_UNARY = DictType.create(STRING, STRING);
+
+ /**
+ * For ListType objects, returns the type of the elements of the list; for
+ * all other types, returns null. (This non-obvious implementation strategy
+ * is necessitated by the wildcard capture rules of the Java type system,
+ * which disallow conversion from Type{List{ELEM}} to Type{List{?}}.)
+ */
+ public Type<?> getListElementType() {
+ return null;
+ }
+
+ /**
+ * ConversionException is thrown when a type-conversion fails; it contains
+ * an explanatory error message.
+ */
+ public static class ConversionException extends EvalException {
+ private static String message(Type<?> type, Object value, String what) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("expected value of type '").append(type).append("'");
+ if (what != null) {
+ builder.append(" for ").append(what);
+ }
+ builder.append(", but got ");
+ Printer.write(builder, value);
+ builder.append(" (").append(EvalUtils.getDataTypeName(value)).append(")");
+ return builder.toString();
+ }
+
+ public ConversionException(Type<?> type, Object value, String what) {
+ super(null, message(type, value, what));
+ }
+
+ public ConversionException(String message) {
+ super(null, message);
+ }
+ }
+
+ /********************************************************************
+ * *
+ * Subclasses *
+ * *
+ ********************************************************************/
+
+ private static class ObjectType extends Type<Object> {
+ @Override
+ public Object cast(Object value) {
+ return value;
+ }
+
+ @Override
+ public String getDefaultValue() {
+ throw new UnsupportedOperationException(
+ "ObjectType has no default value");
+ }
+
+ @Override
+ public Collection<Object> flatten(Object value) {
+ return NOT_COMPOSITE_TYPE;
+ }
+
+ @Override
+ public String toString() {
+ return "object";
+ }
+
+ @Override
+ public Object convert(Object x, String what, Object context) {
+ return x;
+ }
+ }
+
+ private static class IntegerType extends Type<Integer> {
+ @Override
+ public Integer cast(Object value) {
+ return (Integer) value;
+ }
+
+ @Override
+ public Integer getDefaultValue() {
+ return 0;
+ }
+
+ @Override
+ public Collection<Object> flatten(Object value) {
+ return NOT_COMPOSITE_TYPE;
+ }
+
+ @Override
+ public String toString() {
+ return "int";
+ }
+
+ @Override
+ public Integer convert(Object x, String what, Object context)
+ throws ConversionException {
+ if (!(x instanceof Integer)) {
+ throw new ConversionException(this, x, what);
+ }
+ return (Integer) x;
+ }
+
+ @Override
+ public Integer concat(Iterable<Integer> elements) {
+ int ans = 0;
+ for (Integer elem : elements) {
+ ans += elem;
+ }
+ return Integer.valueOf(ans);
+ }
+ }
+
+ private static class BooleanType extends Type<Boolean> {
+ @Override
+ public Boolean cast(Object value) {
+ return (Boolean) value;
+ }
+
+ @Override
+ public Boolean getDefaultValue() {
+ return false;
+ }
+
+ @Override
+ public Collection<Object> flatten(Object value) {
+ return NOT_COMPOSITE_TYPE;
+ }
+
+ @Override
+ public String toString() {
+ return "boolean";
+ }
+
+ // Conversion to boolean must also tolerate integers of 0 and 1 only.
+ @Override
+ public Boolean convert(Object x, String what, Object context)
+ throws ConversionException {
+ if (x instanceof Boolean) {
+ return (Boolean) x;
+ }
+ Integer xAsInteger = INTEGER.convert(x, what, context);
+ if (xAsInteger == 0) {
+ return false;
+ } else if (xAsInteger == 1) {
+ return true;
+ }
+ throw new ConversionException("boolean is not one of [0, 1]");
+ }
+
+ /**
+ * Booleans attributes are converted to tags based on their names.
+ */
+ @Override
+ public Set<String> toTagSet(Object value, String name) {
+ if (value == null) {
+ String msg = "Illegal tag conversion from null on Attribute " + name + ".";
+ throw new IllegalStateException(msg);
+ }
+ String tag = (Boolean) value ? name : "no" + name;
+ return ImmutableSet.of(tag);
+ }
+ }
+
+ private static class StringType extends Type<String> {
+ @Override
+ public String cast(Object value) {
+ return (String) value;
+ }
+
+ @Override
+ public String getDefaultValue() {
+ return "";
+ }
+
+ @Override
+ public Collection<Object> flatten(Object value) {
+ return NOT_COMPOSITE_TYPE;
+ }
+
+ @Override
+ public String toString() {
+ return "string";
+ }
+
+ @Override
+ public String convert(Object x, String what, Object context)
+ throws ConversionException {
+ if (!(x instanceof String)) {
+ throw new ConversionException(this, x, what);
+ }
+ return StringCanonicalizer.intern((String) x);
+ }
+
+ @Override
+ public String concat(Iterable<String> elements) {
+ return Joiner.on("").join(elements);
+ }
+
+ /**
+ * A String is representable as a set containing its value.
+ */
+ @Override
+ public Set<String> toTagSet(Object value, String name) {
+ if (value == null) {
+ String msg = "Illegal tag conversion from null on Attribute " + name + ".";
+ throw new IllegalStateException(msg);
+ }
+ return ImmutableSet.of((String) value);
+ }
+ }
+
+ /**
+ * A type to support dictionary attributes.
+ */
+ public static class DictType<KeyT, ValueT> extends Type<Map<KeyT, ValueT>> {
+
+ private final Type<KeyT> keyType;
+ private final Type<ValueT> valueType;
+
+ private final Map<KeyT, ValueT> empty = ImmutableMap.of();
+
+ public static <KEY, VALUE> DictType<KEY, VALUE> create(
+ Type<KEY> keyType, Type<VALUE> valueType) {
+ return new DictType<>(keyType, valueType);
+ }
+
+ private DictType(Type<KeyT> keyType, Type<ValueT> valueType) {
+ this.keyType = keyType;
+ this.valueType = valueType;
+ }
+
+ public Type<KeyT> getKeyType() {
+ return keyType;
+ }
+
+ public Type<ValueT> getValueType() {
+ return valueType;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map<KeyT, ValueT> cast(Object value) {
+ return (Map<KeyT, ValueT>) value;
+ }
+
+ @Override
+ public String toString() {
+ return "dict(" + keyType + ", " + valueType + ")";
+ }
+
+ @Override
+ public Map<KeyT, ValueT> convert(Object x, String what, Object context)
+ throws ConversionException {
+ if (!(x instanceof Map<?, ?>)) {
+ throw new ConversionException(String.format(
+ "Expected a map for dictionary but got a %s", x.getClass().getName()));
+ }
+ // Order the keys so the return value will be independent of insertion order.
+ Map<KeyT, ValueT> result = new TreeMap<>();
+ Map<?, ?> o = (Map<?, ?>) x;
+ for (Entry<?, ?> elem : o.entrySet()) {
+ result.put(
+ keyType.convert(elem.getKey(), "dict key element", context),
+ valueType.convert(elem.getValue(), "dict value element", context));
+ }
+ return ImmutableMap.copyOf(result);
+ }
+
+ @Override
+ public Map<KeyT, ValueT> getDefaultValue() {
+ return empty;
+ }
+
+ @Override
+ public Collection<Object> flatten(Object value) {
+ ImmutableList.Builder<Object> result = ImmutableList.builder();
+ for (Map.Entry<KeyT, ValueT> entry : cast(value).entrySet()) {
+ result.addAll(keyType.flatten(entry.getKey()));
+ result.addAll(valueType.flatten(entry.getValue()));
+ }
+ return result.build();
+ }
+ }
+
+ /** A type for lists of a given element type */
+ public static class ListType<ElemT> extends Type<List<ElemT>> {
+
+ private final Type<ElemT> elemType;
+
+ private final List<ElemT> empty = ImmutableList.of();
+
+ public static <ELEM> ListType<ELEM> create(Type<ELEM> elemType) {
+ return new ListType<>(elemType);
+ }
+
+ private ListType(Type<ElemT> elemType) {
+ this.elemType = elemType;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<ElemT> cast(Object value) {
+ return (List<ElemT>) value;
+ }
+
+ @Override
+ public Type<ElemT> getListElementType() {
+ return elemType;
+ }
+
+ @Override
+ public List<ElemT> getDefaultValue() {
+ return empty;
+ }
+
+ @Override
+ public Collection<Object> flatten(Object value) {
+ ImmutableList.Builder<Object> labels = ImmutableList.builder();
+ for (ElemT entry : cast(value)) {
+ labels.addAll(elemType.flatten(entry));
+ }
+ return labels.build();
+ }
+
+ @Override
+ public String toString() {
+ return "list(" + elemType + ")";
+ }
+
+ @Override
+ public List<ElemT> convert(Object x, String what, Object context)
+ throws ConversionException {
+ if (!(x instanceof Iterable<?>)) {
+ throw new ConversionException(this, x, what);
+ }
+ int index = 0;
+ Iterable<?> iterable = (Iterable<?>) x;
+ List<ElemT> result = Lists.newArrayListWithExpectedSize(Iterables.size(iterable));
+ for (Object elem : iterable) {
+ ElemT converted = elemType.convert(elem, "element " + index + " of " + what, context);
+ if (converted != null) {
+ result.add(converted);
+ } else {
+ // shouldn't happen but it does, rarely
+ String message = "Converting a list with a null element: "
+ + "element " + index + " of " + what + " in " + context;
+ LoggingUtil.logToRemote(Level.WARNING, message,
+ new ConversionException(message));
+ }
+ ++index;
+ }
+ if (x instanceof GlobList<?>) {
+ return new GlobList<>(((GlobList<?>) x).getCriteria(), result);
+ } else {
+ return result;
+ }
+ }
+
+ @Override
+ public List<ElemT> concat(Iterable<List<ElemT>> elements) {
+ ImmutableList.Builder<ElemT> builder = ImmutableList.builder();
+ for (List<ElemT> list : elements) {
+ builder.addAll(list);
+ }
+ return builder.build();
+ }
+
+ /**
+ * A list is representable as a tag set as the contents of itself expressed
+ * as Strings. So a {@code List<String>} is effectively converted to a {@code Set<String>}.
+ */
+ @Override
+ public Set<String> toTagSet(Object items, String name) {
+ if (items == null) {
+ String msg = "Illegal tag conversion from null on Attribute" + name + ".";
+ throw new IllegalStateException(msg);
+ }
+ Set<String> tags = new LinkedHashSet<>();
+ @SuppressWarnings("unchecked")
+ List<ElemT> itemsAsListofElem = (List<ElemT>) items;
+ for (ElemT element : itemsAsListofElem) {
+ tags.add(element.toString());
+ }
+ return tags;
+ }
+ }
+
+ /** Type for lists of arbitrary objects */
+ public static class ObjectListType extends ListType<Object> {
+
+ private static final Type<Object> elemType = new ObjectType();
+
+ private ObjectListType() {
+ super(elemType);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List<Object> convert(Object x, String what, Object context)
+ throws ConversionException {
+ if (x instanceof SkylarkList) {
+ return ((SkylarkList) x).getList();
+ } else if (x instanceof List) {
+ return (List<Object>) x;
+ } else if (x instanceof Iterable) {
+ return ImmutableList.copyOf((Iterable<?>) x);
+ } else {
+ throw new ConversionException(this, x, what);
+ }
+ }
+ }
+
+ /**
+ * The type of a general list.
+ */
+ public static final ListType<Object> LIST = new ListType<>(new ObjectType());
+}