blob: c4620983735cfee84b48218274e2165071429178 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.syntax;
15
16import com.google.common.annotations.VisibleForTesting;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017import com.google.common.collect.ImmutableList;
18import com.google.common.collect.ImmutableMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.common.collect.Iterables;
Laurent Le Brun196c1a72015-03-18 13:03:04 +000020import com.google.common.collect.Ordering;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.devtools.build.lib.collect.nestedset.NestedSet;
22import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
23import com.google.devtools.build.lib.events.Location;
John Field585d1a02015-12-16 16:03:52 +000024import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
25import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +000026import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
Klaas Boesche53de62a2015-11-06 15:12:10 +000027import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
Mark Schaller6df81792015-12-10 18:47:47 +000028import com.google.devtools.build.lib.util.Preconditions;
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000029import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030
Klaas Boesche53de62a2015-11-06 15:12:10 +000031import net.bytebuddy.implementation.bytecode.StackManipulation;
32
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.util.Collection;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import java.util.List;
35import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036
37/**
38 * Utilities used by the evaluator.
39 */
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000040public final class EvalUtils {
41
42 private EvalUtils() {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043
Laurent Le Brunb54221c2015-04-16 16:14:37 +000044 /**
45 * The exception that SKYLARK_COMPARATOR might throw. This is an unchecked exception
46 * because Comparator doesn't let us declare exceptions. It should normally be caught
47 * and wrapped in an EvalException.
48 */
49 public static class ComparisonException extends RuntimeException {
50 public ComparisonException(String msg) {
51 super(msg);
52 }
53 }
54
55 /**
56 * Compare two Skylark objects.
57 *
58 * <p> It may throw an unchecked exception ComparisonException that should be wrapped in
59 * an EvalException.
60 */
Florian Weikert5e8752b2015-12-11 21:54:43 +000061 public static final Ordering<Object> SKYLARK_COMPARATOR = new Ordering<Object>() {
Laurent Le Brunb54221c2015-04-16 16:14:37 +000062 private int compareLists(SkylarkList o1, SkylarkList o2) {
63 for (int i = 0; i < Math.min(o1.size(), o2.size()); i++) {
64 int cmp = compare(o1.get(i), o2.get(i));
65 if (cmp != 0) {
66 return cmp;
67 }
68 }
69 return Integer.compare(o1.size(), o2.size());
70 }
71
72 @Override
73 @SuppressWarnings("unchecked")
74 public int compare(Object o1, Object o2) {
Francois-Rene Rideau4e994102015-09-17 22:41:28 +000075 o1 = SkylarkType.convertToSkylark(o1, /*env=*/ null);
76 o2 = SkylarkType.convertToSkylark(o2, /*env=*/ null);
Laurent Le Brunb54221c2015-04-16 16:14:37 +000077
Francois-Rene Rideau4e994102015-09-17 22:41:28 +000078 if (o1 instanceof SkylarkList && o2 instanceof SkylarkList
79 && ((SkylarkList) o1).isTuple() == ((SkylarkList) o2).isTuple()) {
Laurent Le Brunb54221c2015-04-16 16:14:37 +000080 return compareLists((SkylarkList) o1, (SkylarkList) o2);
81 }
82 try {
83 return ((Comparable<Object>) o1).compareTo(o2);
84 } catch (ClassCastException e) {
Francois-Rene Rideauf941d562016-01-29 21:51:19 +000085 return compareByClass(o1, o2);
Laurent Le Brunb54221c2015-04-16 16:14:37 +000086 }
87 }
88 };
89
Francois-Rene Rideauf941d562016-01-29 21:51:19 +000090 public static final int compareByClass(Object o1, Object o2) {
91 try {
92 // Different types -> let the class names decide
93 return o1.getClass().getName().compareTo(o2.getClass().getName());
94 } catch (NullPointerException ex) {
95 throw new ComparisonException(
96 "Cannot compare " + getDataTypeName(o1) + " with " + EvalUtils.getDataTypeName(o2));
97 }
98 }
99
Klaas Boesche53de62a2015-11-06 15:12:10 +0000100 public static final StackManipulation checkValidDictKey =
101 ByteCodeUtils.invoke(EvalUtils.class, "checkValidDictKey", Object.class);
102
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100103 /**
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000104 * Checks that an Object is a valid key for a Skylark dict.
105 * @param o an Object to validate
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000106 * @throws EvalException if o is not a valid key
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100107 */
Klaas Boesche53de62a2015-11-06 15:12:10 +0000108 public static void checkValidDictKey(Object o) throws EvalException {
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000109 // TODO(bazel-team): check that all recursive elements are both Immutable AND Comparable.
110 if (isImmutable(o)) {
111 return;
112 }
113 // Same error message as Python (that makes it a TypeError).
114 throw new EvalException(null, Printer.format("unhashable type: '%r'", o.getClass()));
115 }
116
117 /**
118 * Is this object known or assumed to be recursively immutable by Skylark?
119 * @param o an Object
120 * @return true if the object is known to be an immutable value.
121 */
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000122 // NB: This is used as the basis for accepting objects in SkylarkNestedSet-s,
123 // as well as for accepting objects as keys for Skylark dict-s.
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000124 public static boolean isImmutable(Object o) {
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000125 if (o instanceof Tuple) {
126 for (Object item : (Tuple) o) {
127 if (!isImmutable(item)) {
128 return false;
129 }
130 }
131 return true;
132 }
133 if (o instanceof SkylarkMutable) {
134 return false;
135 }
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000136 if (o instanceof SkylarkValue) {
137 return ((SkylarkValue) o).isImmutable();
138 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000139 return isImmutable(o.getClass());
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000140 }
141
142 /**
143 * Is this class known to be *recursively* immutable by Skylark?
144 * For instance, class Tuple is not it, because it can contain mutable values.
145 * @param c a Class
146 * @return true if the class is known to represent only recursively immutable values.
147 */
148 // NB: This is used as the basis for accepting objects in SkylarkNestedSet-s,
149 // as well as for accepting objects as keys for Skylark dict-s.
150 static boolean isImmutable(Class<?> c) {
151 return c.isAnnotationPresent(Immutable.class) // TODO(bazel-team): beware of containers!
152 || c.equals(String.class)
153 || c.equals(Integer.class)
154 || c.equals(Boolean.class);
155 }
156
157 /**
158 * Returns true if the type is acceptable to be returned to the Skylark language.
159 */
160 public static boolean isSkylarkAcceptable(Class<?> c) {
161 return SkylarkValue.class.isAssignableFrom(c) // implements SkylarkValue
162 || c.equals(String.class) // basic values
163 || c.equals(Integer.class)
164 || c.equals(Boolean.class)
165 || c.isAnnotationPresent(SkylarkModule.class) // registered Skylark class
166 || ImmutableMap.class.isAssignableFrom(c) // will be converted to SkylarkDict
167 || NestedSet.class.isAssignableFrom(c) // will be converted to SkylarkNestedSet
168 || c.equals(PathFragment.class); // other known class
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100169 }
170
171 /**
172 * Returns a transitive superclass or interface implemented by c which is annotated
173 * with SkylarkModule. Returns null if no such class or interface exists.
174 */
175 @VisibleForTesting
176 static Class<?> getParentWithSkylarkModule(Class<?> c) {
177 if (c == null) {
178 return null;
179 }
180 if (c.isAnnotationPresent(SkylarkModule.class)) {
181 return c;
182 }
183 Class<?> parent = getParentWithSkylarkModule(c.getSuperclass());
184 if (parent != null) {
185 return parent;
186 }
187 for (Class<?> ifparent : c.getInterfaces()) {
188 ifparent = getParentWithSkylarkModule(ifparent);
189 if (ifparent != null) {
190 return ifparent;
191 }
192 }
193 return null;
194 }
195
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000196 // TODO(bazel-team): move the following few type-related functions to SkylarkType
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100197 /**
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000198 * Return the Skylark-type of {@code c}
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000199 *
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000200 * <p>The result will be a type that Skylark understands and is either equal to {@code c}
201 * or is a supertype of it. For example, all instances of (all subclasses of) SkylarkList
202 * are considered to be SkylarkLists.
203 *
204 * <p>Skylark's type validation isn't equipped to deal with inheritance so we must tell it which
205 * of the superclasses or interfaces of {@code c} is the one that matters for type compatibility.
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000206 *
207 * @param c a class
208 * @return a super-class of c to be used in validation-time type inference.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100209 */
210 public static Class<?> getSkylarkType(Class<?> c) {
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000211 // TODO(bazel-team): replace these with SkylarkValue-s
212 if (String.class.equals(c)
213 || Boolean.class.equals(c)
214 || Integer.class.equals(c)
215 || Iterable.class.equals(c)
216 || Class.class.equals(c)) {
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000217 return c;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100218 }
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000219 // TODO(bazel-team): also unify all implementations of ClassObject,
220 // that we used to all print the same as "struct"?
221 //
222 // Check if one of the superclasses or implemented interfaces has the SkylarkModule
223 // annotation. If yes return that class.
224 Class<?> parent = getParentWithSkylarkModule(c);
225 if (parent != null) {
226 return parent;
227 }
228 Preconditions.checkArgument(SkylarkValue.class.isAssignableFrom(c),
229 "%s is not allowed as a Skylark value", c);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100230 return c;
231 }
232
233 /**
234 * Returns a pretty name for the datatype of object 'o' in the Build language.
235 */
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000236 public static String getDataTypeName(Object o) {
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000237 return getDataTypeName(o, false);
238 }
239
240 /**
241 * Returns a pretty name for the datatype of object {@code object} in Skylark
242 * or the BUILD language, with full details if the {@code full} boolean is true.
243 */
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000244 public static String getDataTypeName(Object object, boolean fullDetails) {
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000245 Preconditions.checkNotNull(object);
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000246 if (fullDetails) {
247 if (object instanceof SkylarkNestedSet) {
248 SkylarkNestedSet set = (SkylarkNestedSet) object;
249 return "set of " + set.getContentType() + "s";
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000250 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000251 if (object instanceof SelectorList) {
252 SelectorList list = (SelectorList) object;
253 return "select of " + getDataTypeNameFromClass(list.getType());
254 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100255 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000256 return getDataTypeNameFromClass(object.getClass());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100257 }
258
259 /**
260 * Returns a pretty name for the datatype equivalent of class 'c' in the Build language.
261 */
262 public static String getDataTypeNameFromClass(Class<?> c) {
Florian Weikertee5e5e12015-08-11 16:47:31 +0000263 return getDataTypeNameFromClass(c, true);
264 }
265
266 /**
267 * Returns a pretty name for the datatype equivalent of class 'c' in the Build language.
268 * @param highlightNameSpaces Determines whether the result should also contain a special comment
269 * when the given class identifies a Skylark name space.
270 */
271 public static String getDataTypeNameFromClass(Class<?> c, boolean highlightNameSpaces) {
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000272 if (c.isAnnotationPresent(SkylarkModule.class)) {
273 SkylarkModule module = c.getAnnotation(SkylarkModule.class);
274 return c.getAnnotation(SkylarkModule.class).name()
275 + ((module.namespace() && highlightNameSpaces) ? " (a language module)" : "");
276 } else if (c.equals(Object.class)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100277 return "unknown";
278 } else if (c.equals(String.class)) {
279 return "string";
280 } else if (c.equals(Integer.class)) {
281 return "int";
282 } else if (c.equals(Boolean.class)) {
283 return "bool";
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000284 } else if (List.class.isAssignableFrom(c)) { // This is a Java List that isn't a SkylarkList
285 return "List"; // This case shouldn't happen in normal code, but we keep it for debugging.
286 } else if (Map.class.isAssignableFrom(c)) { // This is a Java Map that isn't a SkylarkDict
287 return "Map"; // This case shouldn't happen in normal code, but we keep it for debugging.
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000288 } else if (BaseFunction.class.isAssignableFrom(c)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100289 return "function";
Greg Estren92e30d92015-04-09 21:58:28 +0000290 } else if (c.equals(SelectorValue.class)) {
291 return "select";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292 } else if (NestedSet.class.isAssignableFrom(c) || SkylarkNestedSet.class.isAssignableFrom(c)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000293 // TODO(bazel-team): no one should be seeing naked NestedSet at all.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100294 return "set";
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000295 } else if (ClassObject.SkylarkClassObject.class.isAssignableFrom(c)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100296 return "struct";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100297 } else {
298 if (c.getSimpleName().isEmpty()) {
299 return c.getName();
300 } else {
301 return c.getSimpleName();
302 }
303 }
304 }
305
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100306 public static Object checkNotNull(Expression expr, Object obj) throws EvalException {
307 if (obj == null) {
308 throw new EvalException(expr.getLocation(),
309 "Unexpected null value, please send a bug report. "
310 + "This was generated by '" + expr + "'");
311 }
312 return obj;
313 }
314
Klaas Boesche53de62a2015-11-06 15:12:10 +0000315 public static final StackManipulation toBoolean =
316 ByteCodeUtils.invoke(EvalUtils.class, "toBoolean", Object.class);
317
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100318 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100319 * @return the truth value of an object, according to Python rules.
320 * http://docs.python.org/2/library/stdtypes.html#truth-value-testing
321 */
322 public static boolean toBoolean(Object o) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000323 if (o == null || o == Runtime.NONE) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100324 return false;
325 } else if (o instanceof Boolean) {
326 return (Boolean) o;
327 } else if (o instanceof String) {
328 return !((String) o).isEmpty();
329 } else if (o instanceof Integer) {
330 return (Integer) o != 0;
331 } else if (o instanceof Collection<?>) {
332 return !((Collection<?>) o).isEmpty();
333 } else if (o instanceof Map<?, ?>) {
334 return !((Map<?, ?>) o).isEmpty();
335 } else if (o instanceof NestedSet<?>) {
336 return !((NestedSet<?>) o).isEmpty();
337 } else if (o instanceof SkylarkNestedSet) {
338 return !((SkylarkNestedSet) o).isEmpty();
339 } else if (o instanceof Iterable<?>) {
340 return !(Iterables.isEmpty((Iterable<?>) o));
341 } else {
342 return true;
343 }
344 }
345
Klaas Boesche53de62a2015-11-06 15:12:10 +0000346 public static final StackManipulation toCollection =
347 ByteCodeUtils.invoke(EvalUtils.class, "toCollection", Object.class, Location.class);
348
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000349 public static Collection<?> toCollection(Object o, Location loc) throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100350 if (o instanceof Collection) {
Florian Weikert233a46e2015-12-16 12:38:38 +0000351 return (Collection<?>) o;
Laurent Le Brun741824b2015-03-20 15:10:19 +0000352 } else if (o instanceof SkylarkList) {
Laurent Le Brun3d776af2015-12-23 15:10:29 +0000353 return ((SkylarkList) o).getImmutableList();
Florian Weikert233a46e2015-12-16 12:38:38 +0000354 } else if (o instanceof Map) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100355 // For dictionaries we iterate through the keys only
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000356 // For determinism, we sort the keys.
Laurent Le Brunb54221c2015-04-16 16:14:37 +0000357 try {
Florian Weikert233a46e2015-12-16 12:38:38 +0000358 return SKYLARK_COMPARATOR.sortedCopy(((Map<?, ?>) o).keySet());
Laurent Le Brunb54221c2015-04-16 16:14:37 +0000359 } catch (ComparisonException e) {
360 throw new EvalException(loc, e);
361 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100362 } else if (o instanceof SkylarkNestedSet) {
363 return ((SkylarkNestedSet) o).toCollection();
364 } else {
365 throw new EvalException(loc,
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000366 "type '" + getDataTypeName(o) + "' is not a collection");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100367 }
368 }
369
Klaas Boesche53de62a2015-11-06 15:12:10 +0000370 public static final StackManipulation toIterable =
371 ByteCodeUtils.invoke(EvalUtils.class, "toIterable", Object.class, Location.class);
372
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000373 public static Iterable<?> toIterable(Object o, Location loc) throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100374 if (o instanceof String) {
375 // This is not as efficient as special casing String in for and dict and list comprehension
376 // statements. However this is a more unified way.
Florian Weikert233a46e2015-12-16 12:38:38 +0000377 return split((String) o);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100378 } else if (o instanceof Iterable) {
Florian Weikert233a46e2015-12-16 12:38:38 +0000379 return (Iterable<?>) o;
380 } else if (o instanceof Map) {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000381 return toCollection(o, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100382 } else {
383 throw new EvalException(loc,
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000384 "type '" + getDataTypeName(o) + "' is not iterable");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385 }
386 }
387
Florian Weikert233a46e2015-12-16 12:38:38 +0000388 private static ImmutableList<String> split(String value) {
389 ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
390 for (char c : value.toCharArray()) {
391 builder.add(String.valueOf(c));
392 }
393 return builder.build();
394 }
395
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100396 /**
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000397 * @return the size of the Skylark object or -1 in case the object doesn't have a size.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100398 */
399 public static int size(Object arg) {
400 if (arg instanceof String) {
401 return ((String) arg).length();
402 } else if (arg instanceof Map) {
403 return ((Map<?, ?>) arg).size();
404 } else if (arg instanceof SkylarkList) {
405 return ((SkylarkList) arg).size();
406 } else if (arg instanceof Iterable) {
407 // Iterables.size() checks if arg is a Collection so it's efficient in that sense.
408 return Iterables.size((Iterable<?>) arg);
409 }
410 return -1;
411 }
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000412
413 /** @return true if x is Java null or Skylark None */
414 public static boolean isNullOrNone(Object x) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000415 return x == null || x == Runtime.NONE;
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000416 }
417
418 /**
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000419 * Build a SkylarkDict of kwarg arguments from a list, removing null-s or None-s.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000420 *
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000421 * @param env the Environment in which this map can be mutated.
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000422 * @param init a series of key, value pairs (as consecutive arguments)
Francois-Rene Rideau328f6152016-01-06 19:50:27 +0000423 * as in {@code optionMap(k1, v1, k2, v2, k3, v3)}
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000424 * where each key is a String, each value is an arbitrary Objet.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000425 * @return a {@code Map<String, Object>} that has all the specified entries,
426 * where key, value pairs appearing earlier have precedence,
427 * i.e. {@code k1, v1} may override {@code k3, v3}.
428 *
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000429 * Ignore any entry where the value is null or None.
430 * Keys cannot be null.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000431 */
432 @SuppressWarnings("unchecked")
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000433 public static <K, V> SkylarkDict<K, V> optionMap(Environment env, Object... init) {
434 ImmutableMap.Builder<K, V> b = new ImmutableMap.Builder<>();
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000435 Preconditions.checkState(init.length % 2 == 0);
436 for (int i = init.length - 2; i >= 0; i -= 2) {
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000437 K key = (K) Preconditions.checkNotNull(init[i]);
438 V value = (V) init[i + 1];
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000439 if (!isNullOrNone(value)) {
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000440 b.put(key, value);
441 }
442 }
Francois-Rene Rideauf941d562016-01-29 21:51:19 +0000443 return SkylarkDict.<K, V>copyOf(env, b.build());
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000444 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100445}