diff options
author | 2015-09-18 11:40:12 +0000 | |
---|---|---|
committer | 2015-09-21 08:56:51 +0000 | |
commit | ffa73addaf80ab7a9cf9f98e5d4414b152ae6cb6 (patch) | |
tree | f498621bb8634d934815dbaddf90400eda0ce28d /src/main/java/com/google/devtools/build/lib/syntax | |
parent | 29ad862a5bd45d2fdbb921f36bbb95816cf47acc (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')
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<String> 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()); +} |