blob: 0d001d301621a28d61d533c1c6a811f1acbf2f41 [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;
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +000036import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037
38/**
39 * Utilities used by the evaluator.
40 */
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000041public final class EvalUtils {
42
43 private EvalUtils() {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044
Laurent Le Brunb54221c2015-04-16 16:14:37 +000045 /**
46 * The exception that SKYLARK_COMPARATOR might throw. This is an unchecked exception
47 * because Comparator doesn't let us declare exceptions. It should normally be caught
48 * and wrapped in an EvalException.
49 */
50 public static class ComparisonException extends RuntimeException {
51 public ComparisonException(String msg) {
52 super(msg);
53 }
54 }
55
56 /**
57 * Compare two Skylark objects.
58 *
59 * <p> It may throw an unchecked exception ComparisonException that should be wrapped in
60 * an EvalException.
61 */
Florian Weikert5e8752b2015-12-11 21:54:43 +000062 public static final Ordering<Object> SKYLARK_COMPARATOR = new Ordering<Object>() {
Laurent Le Brunb54221c2015-04-16 16:14:37 +000063 private int compareLists(SkylarkList o1, SkylarkList o2) {
64 for (int i = 0; i < Math.min(o1.size(), o2.size()); i++) {
65 int cmp = compare(o1.get(i), o2.get(i));
66 if (cmp != 0) {
67 return cmp;
68 }
69 }
70 return Integer.compare(o1.size(), o2.size());
71 }
72
73 @Override
74 @SuppressWarnings("unchecked")
75 public int compare(Object o1, Object o2) {
Francois-Rene Rideau4e994102015-09-17 22:41:28 +000076 o1 = SkylarkType.convertToSkylark(o1, /*env=*/ null);
77 o2 = SkylarkType.convertToSkylark(o2, /*env=*/ null);
Laurent Le Brunb54221c2015-04-16 16:14:37 +000078
Francois-Rene Rideau4e994102015-09-17 22:41:28 +000079 if (o1 instanceof SkylarkList && o2 instanceof SkylarkList
80 && ((SkylarkList) o1).isTuple() == ((SkylarkList) o2).isTuple()) {
Laurent Le Brunb54221c2015-04-16 16:14:37 +000081 return compareLists((SkylarkList) o1, (SkylarkList) o2);
82 }
83 try {
84 return ((Comparable<Object>) o1).compareTo(o2);
85 } catch (ClassCastException e) {
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +000086 try {
87 // Different types -> let the class names decide
88 return o1.getClass().getName().compareTo(o2.getClass().getName());
89 } catch (NullPointerException ex) {
90 throw new ComparisonException(
91 "Cannot compare " + getDataTypeName(o1) + " with " + EvalUtils.getDataTypeName(o2));
92 }
Laurent Le Brunb54221c2015-04-16 16:14:37 +000093 }
94 }
95 };
96
Klaas Boesche53de62a2015-11-06 15:12:10 +000097 public static final StackManipulation checkValidDictKey =
98 ByteCodeUtils.invoke(EvalUtils.class, "checkValidDictKey", Object.class);
99
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100100 /**
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000101 * Checks that an Object is a valid key for a Skylark dict.
102 * @param o an Object to validate
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000103 * @throws EvalException if o is not a valid key
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100104 */
Klaas Boesche53de62a2015-11-06 15:12:10 +0000105 public static void checkValidDictKey(Object o) throws EvalException {
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000106 // TODO(bazel-team): check that all recursive elements are both Immutable AND Comparable.
107 if (isImmutable(o)) {
108 return;
109 }
110 // Same error message as Python (that makes it a TypeError).
111 throw new EvalException(null, Printer.format("unhashable type: '%r'", o.getClass()));
112 }
113
114 /**
115 * Is this object known or assumed to be recursively immutable by Skylark?
116 * @param o an Object
117 * @return true if the object is known to be an immutable value.
118 */
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000119 // NB: This is used as the basis for accepting objects in SkylarkNestedSet-s,
120 // as well as for accepting objects as keys for Skylark dict-s.
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000121 public static boolean isImmutable(Object o) {
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000122 if (o instanceof Tuple) {
123 for (Object item : (Tuple) o) {
124 if (!isImmutable(item)) {
125 return false;
126 }
127 }
128 return true;
129 }
130 if (o instanceof SkylarkMutable) {
131 return false;
132 }
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000133 if (o instanceof SkylarkValue) {
134 return ((SkylarkValue) o).isImmutable();
135 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000136 return isImmutable(o.getClass());
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000137 }
138
139 /**
140 * Is this class known to be *recursively* immutable by Skylark?
141 * For instance, class Tuple is not it, because it can contain mutable values.
142 * @param c a Class
143 * @return true if the class is known to represent only recursively immutable values.
144 */
145 // NB: This is used as the basis for accepting objects in SkylarkNestedSet-s,
146 // as well as for accepting objects as keys for Skylark dict-s.
147 static boolean isImmutable(Class<?> c) {
148 return c.isAnnotationPresent(Immutable.class) // TODO(bazel-team): beware of containers!
149 || c.equals(String.class)
150 || c.equals(Integer.class)
151 || c.equals(Boolean.class);
152 }
153
154 /**
155 * Returns true if the type is acceptable to be returned to the Skylark language.
156 */
157 public static boolean isSkylarkAcceptable(Class<?> c) {
158 return SkylarkValue.class.isAssignableFrom(c) // implements SkylarkValue
159 || c.equals(String.class) // basic values
160 || c.equals(Integer.class)
161 || c.equals(Boolean.class)
162 || c.isAnnotationPresent(SkylarkModule.class) // registered Skylark class
163 || ImmutableMap.class.isAssignableFrom(c) // will be converted to SkylarkDict
164 || NestedSet.class.isAssignableFrom(c) // will be converted to SkylarkNestedSet
165 || c.equals(PathFragment.class); // other known class
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100166 }
167
168 /**
169 * Returns a transitive superclass or interface implemented by c which is annotated
170 * with SkylarkModule. Returns null if no such class or interface exists.
171 */
172 @VisibleForTesting
173 static Class<?> getParentWithSkylarkModule(Class<?> c) {
174 if (c == null) {
175 return null;
176 }
177 if (c.isAnnotationPresent(SkylarkModule.class)) {
178 return c;
179 }
180 Class<?> parent = getParentWithSkylarkModule(c.getSuperclass());
181 if (parent != null) {
182 return parent;
183 }
184 for (Class<?> ifparent : c.getInterfaces()) {
185 ifparent = getParentWithSkylarkModule(ifparent);
186 if (ifparent != null) {
187 return ifparent;
188 }
189 }
190 return null;
191 }
192
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000193 // TODO(bazel-team): move the following few type-related functions to SkylarkType
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100194 /**
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000195 * Return the Skylark-type of {@code c}
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000196 *
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000197 * <p>The result will be a type that Skylark understands and is either equal to {@code c}
198 * or is a supertype of it. For example, all instances of (all subclasses of) SkylarkList
199 * are considered to be SkylarkLists.
200 *
201 * <p>Skylark's type validation isn't equipped to deal with inheritance so we must tell it which
202 * of the superclasses or interfaces of {@code c} is the one that matters for type compatibility.
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000203 *
204 * @param c a class
205 * @return a super-class of c to be used in validation-time type inference.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100206 */
207 public static Class<?> getSkylarkType(Class<?> c) {
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +0000208 if (SkylarkList.class.isAssignableFrom(c)) {
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000209 return c;
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +0000210 } else if (ImmutableList.class.isAssignableFrom(c)) {
211 return ImmutableList.class;
212 } else if (List.class.isAssignableFrom(c)) {
213 return List.class;
214 } else if (Map.class.isAssignableFrom(c)) {
215 return Map.class;
216 } else if (NestedSet.class.isAssignableFrom(c)) {
217 // This could be removed probably
218 return NestedSet.class;
219 } else if (Set.class.isAssignableFrom(c)) {
220 return Set.class;
221 } else {
222 // TODO(bazel-team): also unify all implementations of ClassObject,
223 // that we used to all print the same as "struct"?
224 //
225 // Check if one of the superclasses or implemented interfaces has the SkylarkModule
226 // annotation. If yes return that class.
227 Class<?> parent = getParentWithSkylarkModule(c);
228 if (parent != null) {
229 return parent;
230 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100231 }
232 return c;
233 }
234
235 /**
236 * Returns a pretty name for the datatype of object 'o' in the Build language.
237 */
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000238 public static String getDataTypeName(Object o) {
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000239 return getDataTypeName(o, false);
240 }
241
242 /**
243 * Returns a pretty name for the datatype of object {@code object} in Skylark
244 * or the BUILD language, with full details if the {@code full} boolean is true.
245 */
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000246 public static String getDataTypeName(Object object, boolean fullDetails) {
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000247 Preconditions.checkNotNull(object);
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000248 if (fullDetails) {
249 if (object instanceof SkylarkNestedSet) {
250 SkylarkNestedSet set = (SkylarkNestedSet) object;
251 return "set of " + set.getContentType() + "s";
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000252 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000253 if (object instanceof SelectorList) {
254 SelectorList list = (SelectorList) object;
255 return "select of " + getDataTypeNameFromClass(list.getType());
256 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100257 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000258 return getDataTypeNameFromClass(object.getClass());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100259 }
260
261 /**
262 * Returns a pretty name for the datatype equivalent of class 'c' in the Build language.
263 */
264 public static String getDataTypeNameFromClass(Class<?> c) {
Florian Weikertee5e5e12015-08-11 16:47:31 +0000265 return getDataTypeNameFromClass(c, true);
266 }
267
268 /**
269 * Returns a pretty name for the datatype equivalent of class 'c' in the Build language.
270 * @param highlightNameSpaces Determines whether the result should also contain a special comment
271 * when the given class identifies a Skylark name space.
272 */
273 public static String getDataTypeNameFromClass(Class<?> c, boolean highlightNameSpaces) {
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000274 if (c.isAnnotationPresent(SkylarkModule.class)) {
275 SkylarkModule module = c.getAnnotation(SkylarkModule.class);
276 return c.getAnnotation(SkylarkModule.class).name()
277 + ((module.namespace() && highlightNameSpaces) ? " (a language module)" : "");
278 } else if (c.equals(Object.class)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100279 return "unknown";
280 } else if (c.equals(String.class)) {
281 return "string";
282 } else if (c.equals(Integer.class)) {
283 return "int";
284 } else if (c.equals(Boolean.class)) {
285 return "bool";
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +0000286 } else if (Map.class.isAssignableFrom(c)) {
287 return "dict";
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 /**
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +0000419 * Build a map of kwarg arguments from a list, removing null-s or None-s.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000420 *
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000421 * @param init a series of key, value pairs (as consecutive arguments)
Francois-Rene Rideau328f6152016-01-06 19:50:27 +0000422 * as in {@code optionMap(k1, v1, k2, v2, k3, v3)}
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000423 * where each key is a String, each value is an arbitrary Objet.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000424 * @return a {@code Map<String, Object>} that has all the specified entries,
425 * where key, value pairs appearing earlier have precedence,
426 * i.e. {@code k1, v1} may override {@code k3, v3}.
427 *
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000428 * Ignore any entry where the value is null or None.
429 * Keys cannot be null.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000430 */
431 @SuppressWarnings("unchecked")
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +0000432 public static ImmutableMap<String, Object> optionMap(Object... init) {
433 ImmutableMap.Builder<String, Object> b = new ImmutableMap.Builder<>();
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000434 Preconditions.checkState(init.length % 2 == 0);
435 for (int i = init.length - 2; i >= 0; i -= 2) {
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +0000436 String key = (String) Preconditions.checkNotNull(init[i]);
437 Object value = init[i + 1];
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000438 if (!isNullOrNone(value)) {
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000439 b.put(key, value);
440 }
441 }
Damien Martin-Guillerezfbd83332016-01-29 15:22:51 +0000442 return b.build();
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000443 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100444}