| // Copyright 2014 The Bazel Authors. 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.packages; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| 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.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.Depset; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; |
| import com.google.devtools.build.lib.util.LoggingUtil; |
| import com.google.devtools.build.lib.util.StringCanonicalizer; |
| import java.util.ArrayList; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.RandomAccess; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.Dict; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Printer; |
| import net.starlark.java.eval.Sequence; |
| import net.starlark.java.eval.Starlark; |
| import net.starlark.java.eval.StarlarkInt; |
| |
| /** |
| * Root of Type symbol hierarchy for values in the build language. |
| * |
| * <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>For example, this code type-converts a value <code>x</code> returned by the evaluator, to a |
| * list of strings: |
| * |
| * <pre> |
| * Object x = expr.eval(env); |
| * List<String> s = Type.STRING_LIST.convert(x); |
| * </pre> |
| * |
| * <p><b>BEFORE YOU ADD A NEW TYPE:</b> |
| * |
| * <p>We frequently get requests to create a new kind of attribute type whenever a use case doesn't |
| * seem to fit into one of the existing types. This is almost always a bad idea. The most complex |
| * type we currently have is probably STRING_LIST_DICT or maybe LABEL_KEYED_STRING_DICT. But no |
| * matter what you support, someone will always want to add another layer of structure. It's even |
| * been suggested to allow JSON or arbitrary Starlark values in attributes. |
| * |
| * <p>Adding a new type has implications for many different systems. The whole of the loading phase |
| * needs to know about the type -- how to serialize it, how to format it for `bazel query`, how to |
| * traverse label dependencies embedded within it. Then you need to think about how to represent |
| * attribute values of that type in Starlark within a rule implementation function, and come up with |
| * a good name for that type in the Starlark `attr` module. All of the tooling for formatting, |
| * linting, and analyzing BUILD files may need to be updated. |
| * |
| * <p>It's usually possible to accomplish the end goal without making the target attribute grammar |
| * more expressive. If it's not, that may be a sign that attributes are not the right mechanism to |
| * use, and perhaps instead you should use opaque string identifiers, or labels to sub-targets with |
| * more structure (think toolchains, platforms, config_setting). |
| * |
| * <p>Any new attribute type should be general-purpose and meet a high bar of usefulness (unlikely |
| * since we seem to be doing fine so far without it), and not overly complicate BUILD files or rule |
| * implementation functions. |
| */ |
| // TODO(adonovan): update documentation here and elsewhere to use the term |
| // "rule attribute values" or "valid attribute types" where appropriate, |
| // and not "value in the build language", which is a much broader set of |
| // possible Starlark values. Also link to the canonical set of valid attribute |
| // types, both Starlark and native. |
| public abstract class Type<T> { |
| |
| Type() {} |
| |
| /** |
| * Converts a legal Starlark value x into an Java value 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 Starlark value to convert. |
| * @param what An object whose toString method returns a description of the purpose of x. |
| * Typically it is the name of a function parameter or struct field. The method is called only |
| * in case of error. |
| * @param labelConverter the converter to use to convert label literals to Label objects; must be |
| * non-null if parsing non-canonical label strings is required |
| * @throws ConversionException if there was a problem performing the type conversion |
| * @throws NullPointerException if x is null. |
| */ |
| public abstract T convert(Object x, Object what, @Nullable LabelConverter labelConverter) |
| throws ConversionException; |
| // TODO(bazel-team): Check external calls (e.g. in PackageFactory), verify they always want |
| // this over selectableConvert. |
| |
| /** |
| * Equivalent to {@link #convert(Object, Object, LabelConverter)} where the label is {@code null}. |
| * Useful for converting values to types that do not involve the type {@code LABEL}. |
| */ |
| public final T convert(Object x, Object what) throws ConversionException { |
| return convert(x, what, null); |
| } |
| |
| /** |
| * Like {@link #convert(Object, Object, LabelConverter)}, but converts Starlark {@code None} to |
| * given {@code defaultValue}. |
| */ |
| @Nullable |
| public final T convertOptional( |
| Object x, String what, @Nullable LabelConverter labelConverter, T defaultValue) |
| throws ConversionException { |
| if (Starlark.isNullOrNone(x)) { |
| return defaultValue; |
| } |
| return convert(x, what, labelConverter); |
| } |
| |
| /** |
| * Like {@link #convert(Object, Object, LabelConverter)}, but converts Starlark {@code None} to |
| * java {@code null}. |
| */ |
| @Nullable |
| public final T convertOptional(Object x, String what, @Nullable LabelConverter labelConverter) |
| throws ConversionException { |
| return convertOptional(x, what, labelConverter, null); |
| } |
| |
| /** |
| * Like {@link #convert(Object, Object)}, but converts Starlark {@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(); |
| |
| /** |
| * Function accepting a (potentially null) {@link Label} and a (potentially null) {@link |
| * Attribute} provided as context. Used by {@link #visitLabels}. |
| */ |
| public interface LabelVisitor { |
| void visit(@Nullable Label label, @Nullable Attribute context); |
| } |
| |
| /** |
| * Invokes {@code visitor.visit(label, context)} for each {@link Label} {@code label} associated |
| * with {@code value}, an instance of this {@link Type}. |
| * |
| * <p>This is used to support reliable label visitation in {@link |
| * com.google.devtools.build.lib.packages.AttributeMap#visitAllLabels}. 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 void visitLabels(LabelVisitor visitor, T value, @Nullable Attribute context); |
| |
| /** Classifications of labels by their usage. */ |
| public enum LabelClass { |
| /** Used for types which are not labels. */ |
| NONE, |
| /** Used for types which use labels to declare a dependency. */ |
| DEPENDENCY, |
| /** |
| * Used for types which use labels to reference another target but do not declare a dependency, |
| * in cases where doing so would cause a dependency cycle. |
| */ |
| NONDEP_REFERENCE, |
| /** |
| * Used for types which declare a dependency, but the dependency should not be configured. Used |
| * when the label is used only in the loading phase. e.g. genquery.scope |
| */ |
| GENQUERY_SCOPE_REFERENCE, |
| /** Used for types which use labels to declare an output path. */ |
| OUTPUT, |
| /** |
| * Used for types which contain Fileset entries, which contain labels but do not produce normal |
| * dependencies. |
| */ |
| FILESET_ENTRY |
| } |
| |
| /** Returns the class of labels contained by this type, if any. */ |
| public LabelClass getLabelClass() { |
| return LabelClass.NONE; |
| } |
| |
| /** |
| * Implementation of concatenation for this type, as if by {@code elements[0] + ... + |
| * elements[n-1]}) for scalars or lists, or {@code elements[0] | ... | elements[n-1]} for dicts. |
| * Returns null to indicate concatenation isn't supported. |
| * |
| * <p>This method exists to support deferred additions {@code select + T} for catenable types T |
| * such as string, int, list, and deferred unions {@code select | T} for map types T. |
| */ |
| 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 a Starlark integer in the signed 32-bit range. */ |
| @SerializationConstant public static final Type<StarlarkInt> INTEGER = new IntegerType(); |
| |
| /** The type of a string. */ |
| @SerializationConstant public static final Type<String> STRING = new StringType(); |
| |
| /** The type of a boolean. */ |
| @SerializationConstant public static final Type<Boolean> BOOLEAN = new BooleanType(); |
| |
| /** The type of a list of not-yet-typed objects. */ |
| @SerializationConstant public static final ObjectListType OBJECT_LIST = new ObjectListType(); |
| |
| /** The type of a list of strings. */ |
| @SerializationConstant public static final ListType<String> STRING_LIST = ListType.create(STRING); |
| |
| /** The type of a list of signed 32-bit Starlark integer values. */ |
| @SerializationConstant |
| public static final ListType<StarlarkInt> INTEGER_LIST = ListType.create(INTEGER); |
| |
| /** The type of a dictionary of {@linkplain #STRING strings}. */ |
| @SerializationConstant |
| public static final DictType<String, String> STRING_DICT = DictType.create(STRING, STRING); |
| |
| /** The type of a dictionary of {@linkplain #STRING_LIST label lists}. */ |
| @SerializationConstant |
| public static final DictType<String, List<String>> STRING_LIST_DICT = |
| DictType.create(STRING, STRING_LIST); |
| |
| /** |
| * 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, @Nullable Object what) { |
| Printer printer = new Printer(); |
| printer.append("expected value of type '").append(type.toString()).append("'"); |
| if (what != null) { |
| printer.append(" for ").append(what.toString()); |
| } |
| printer.append(", but got "); |
| printer.repr(value); |
| printer.append(" (").append(Starlark.type(value)).append(")"); |
| return printer.toString(); |
| } |
| |
| /** Contructs a conversion error. Throws NullPointerException if value is null. */ |
| ConversionException(Type<?> type, Object value, @Nullable Object what) { |
| super(message(type, Preconditions.checkNotNull(value), what)); |
| } |
| |
| public ConversionException(String message) { |
| super(message); |
| } |
| } |
| |
| /******************************************************************** |
| * * |
| * Subclasses * |
| * * |
| ********************************************************************/ |
| |
| private static final 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 void visitLabels(LabelVisitor visitor, Object value, @Nullable Attribute context) {} |
| |
| @Override |
| public String toString() { |
| return "object"; |
| } |
| |
| @Override |
| public Object convert(Object x, Object what, LabelConverter labelConverter) { |
| return Preconditions.checkNotNull(x); |
| } |
| } |
| |
| // A Starlark integer in the signed 32-bit range (like Java int). |
| private static final class IntegerType extends Type<StarlarkInt> { |
| @Override |
| public StarlarkInt cast(Object value) { |
| // This cast will fail if passed a java.lang.Integer, |
| // as it is not a legal Starlark value. Use StarlarkInt. |
| return (StarlarkInt) value; |
| } |
| |
| @Override |
| public StarlarkInt getDefaultValue() { |
| return StarlarkInt.of(0); |
| } |
| |
| @Override |
| public void visitLabels(LabelVisitor visitor, StarlarkInt value, @Nullable Attribute context) {} |
| |
| @Override |
| public String toString() { |
| return "int"; |
| } |
| |
| @Override |
| public StarlarkInt convert(Object x, Object what, LabelConverter labelConverter) |
| throws ConversionException { |
| if (x instanceof StarlarkInt) { |
| StarlarkInt i = (StarlarkInt) x; |
| try { |
| i.toIntUnchecked(); // assert signed 32-bit |
| } catch ( |
| @SuppressWarnings("UnusedException") |
| IllegalArgumentException ex) { |
| String prefix = what != null ? ("for " + what + ", ") : ""; |
| throw new ConversionException( |
| String.format("%sgot %s, want value in signed 32-bit range", prefix, i)); |
| } |
| return i; |
| } |
| if (x instanceof Integer) { |
| throw new IllegalArgumentException("Integer is not a legal Starlark value"); |
| } |
| throw new ConversionException(this, x, what); |
| } |
| |
| @Override |
| public StarlarkInt concat(Iterable<StarlarkInt> elements) { |
| StarlarkInt sum = StarlarkInt.of(0); |
| for (StarlarkInt elem : elements) { |
| sum = StarlarkInt.add(sum, elem); |
| } |
| // Perform narrowing conversion to ensure that the result |
| // remains in the signed 32-bit range. This means that |
| // s=select(0x7fffffff); s+s may yield a negative result. |
| return StarlarkInt.of(sum.truncateToInt()); |
| } |
| } |
| |
| private static final class BooleanType extends Type<Boolean> { |
| @Override |
| public Boolean cast(Object value) { |
| return (Boolean) value; |
| } |
| |
| @Override |
| public Boolean getDefaultValue() { |
| return false; |
| } |
| |
| @Override |
| public void visitLabels(LabelVisitor visitor, Boolean value, @Nullable Attribute context) {} |
| |
| @Override |
| public String toString() { |
| return "boolean"; |
| } |
| |
| // Conversion to boolean must also tolerate integers of 0 and 1 only. |
| @Override |
| public Boolean convert(Object x, Object what, LabelConverter labelConverter) |
| throws ConversionException { |
| if (x instanceof Boolean) { |
| return (Boolean) x; |
| } |
| int xAsInteger = INTEGER.convert(x, what, labelConverter).toIntUnchecked(); |
| 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 final class StringType extends Type<String> { |
| @Override |
| public String cast(Object value) { |
| return (String) value; |
| } |
| |
| @Override |
| public String getDefaultValue() { |
| return ""; |
| } |
| |
| @Override |
| public void visitLabels(LabelVisitor visitor, String value, @Nullable Attribute context) {} |
| |
| @Override |
| public String toString() { |
| return "string"; |
| } |
| |
| @Override |
| public String convert(Object x, Object what, LabelConverter labelConverter) |
| 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(); |
| |
| private final LabelClass labelClass; |
| |
| @Override |
| public final void visitLabels( |
| LabelVisitor visitor, Map<KeyT, ValueT> value, @Nullable Attribute context) { |
| if (labelClass != LabelClass.NONE) { |
| for (Map.Entry<KeyT, ValueT> entry : value.entrySet()) { |
| keyType.visitLabels(visitor, entry.getKey(), context); |
| valueType.visitLabels(visitor, entry.getValue(), context); |
| } |
| } |
| } |
| |
| public static <KEY, VALUE> DictType<KEY, VALUE> create( |
| Type<KEY> keyType, Type<VALUE> valueType) { |
| LabelClass keyLabelClass = keyType.getLabelClass(); |
| LabelClass valueLabelClass = valueType.getLabelClass(); |
| Preconditions.checkArgument( |
| keyLabelClass == LabelClass.NONE |
| || valueLabelClass == LabelClass.NONE |
| || keyLabelClass == valueLabelClass, |
| "A DictType's keys and values must be the same class of label if both contain labels, " |
| + "but the key type %s contains %s labels, while " |
| + "the value type %s contains %s labels.", |
| keyType, |
| keyLabelClass, |
| valueType, |
| valueLabelClass); |
| LabelClass labelClass = (keyLabelClass != LabelClass.NONE) ? keyLabelClass : valueLabelClass; |
| |
| return new DictType<>(keyType, valueType, labelClass); |
| } |
| |
| DictType(Type<KeyT> keyType, Type<ValueT> valueType, LabelClass labelClass) { |
| this.keyType = keyType; |
| this.valueType = valueType; |
| this.labelClass = labelClass; |
| } |
| |
| public Type<KeyT> getKeyType() { |
| return keyType; |
| } |
| |
| public Type<ValueT> getValueType() { |
| return valueType; |
| } |
| |
| @Override |
| public LabelClass getLabelClass() { |
| return labelClass; |
| } |
| |
| @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, Object what, LabelConverter labelConverter) |
| throws ConversionException { |
| if (!(x instanceof Map)) { |
| throw new ConversionException(this, x, what); |
| } |
| Map<?, ?> o = (Map<?, ?>) x; |
| // It's possible that #convert() calls transform non-equal keys into equal ones so we can't |
| // just use ImmutableMap.Builder() here (that throws on collisions). |
| LinkedHashMap<KeyT, ValueT> result = new LinkedHashMap<>(); |
| for (Map.Entry<?, ?> elem : o.entrySet()) { |
| result.put( |
| keyType.convert(elem.getKey(), "dict key element", labelConverter), |
| valueType.convert(elem.getValue(), "dict value element", labelConverter)); |
| } |
| return ImmutableMap.copyOf(result); |
| } |
| |
| @Override |
| public Map<KeyT, ValueT> concat(Iterable<Map<KeyT, ValueT>> iterable) { |
| Dict.Builder<KeyT, ValueT> output = new Dict.Builder<>(); |
| for (Map<KeyT, ValueT> map : iterable) { |
| output.putAll(map); |
| } |
| return output.buildImmutable(); |
| } |
| |
| @Override |
| public Map<KeyT, ValueT> getDefaultValue() { |
| return empty; |
| } |
| } |
| |
| /** 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 final List<ElemT> cast(Object value) { |
| return (List<ElemT>) value; |
| } |
| |
| @Override |
| public Type<ElemT> getListElementType() { |
| return elemType; |
| } |
| |
| @Override |
| public LabelClass getLabelClass() { |
| return elemType.getLabelClass(); |
| } |
| |
| @Override |
| public List<ElemT> getDefaultValue() { |
| return empty; |
| } |
| |
| @Override |
| public final void visitLabels( |
| LabelVisitor visitor, List<ElemT> value, @Nullable Attribute context) { |
| if (elemType.getLabelClass() == LabelClass.NONE) { |
| return; |
| } |
| |
| // Hot code path. Optimize for lists with O(1) access to avoid iterator garbage. |
| if (value instanceof RandomAccess) { |
| for (int i = 0; i < value.size(); i++) { |
| elemType.visitLabels(visitor, value.get(i), context); |
| } |
| } else { |
| for (ElemT elem : value) { |
| elemType.visitLabels(visitor, elem, context); |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "list(" + elemType + ")"; |
| } |
| |
| @Override |
| public List<ElemT> convert(Object x, Object what, LabelConverter labelConverter) |
| throws ConversionException { |
| Iterable<?> iterable; |
| |
| if (x instanceof Iterable) { |
| iterable = (Iterable<?>) x; |
| } else if (x instanceof Depset) { |
| iterable = ((Depset) x).toList(); |
| } else { |
| throw new ConversionException(this, x, what); |
| } |
| |
| int index = 0; |
| List<ElemT> result = new ArrayList<>(Iterables.size(iterable)); |
| ListConversionContext conversionContext = new ListConversionContext(what); |
| for (Object elem : iterable) { |
| conversionContext.update(index); |
| ElemT converted = elemType.convert(elem, conversionContext, labelConverter); |
| 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 " |
| + labelConverter; |
| LoggingUtil.logToRemote(Level.WARNING, message, |
| new ConversionException(message)); |
| } |
| ++index; |
| } |
| 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; |
| } |
| |
| /** |
| * Provides a {@link #toString()} description of the context of the value in a list being |
| * converted. This is preferred over a raw string to avoid uselessly constructing strings which |
| * are never used. This class is mutable (the index is updated). |
| */ |
| private static class ListConversionContext { |
| private final Object what; |
| private int index = 0; |
| |
| ListConversionContext(Object what) { |
| this.what = what; |
| } |
| |
| void update(int index) { |
| this.index = index; |
| } |
| |
| @Override |
| public String toString() { |
| return "element " + index + " of " + what; |
| } |
| } |
| } |
| |
| /** 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, Object what, LabelConverter labelConverter) |
| throws ConversionException { |
| // TODO(adonovan): converge on Starlark.toIterable. |
| if (x instanceof Sequence) { |
| return ((Sequence<Object>) x).getImmutableList(); |
| } 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); |
| } |
| } |
| } |
| } |