aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
blob: ef9fe1014ea81e00cd0b42410069f5578b034a70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// 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.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.events.Location;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * A class to handle lists and tuples in Skylark.
 */
@SkylarkModule(name = "list",
    doc = "A language built-in type to support lists. Example of list literal:<br>"
        + "<pre class=language-python>l = [1, 2, 3]</pre>"
        + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>"
        + "<pre class=language-python>e = l[1]   # e == 2</pre>"
        + "Lists support the <code>+</code> operator to concatenate two lists. Example:<br>"
        + "<pre class=language-python>l = [1, 2] + [3, 4]   # l == [1, 2, 3, 4]\n"
        + "l = [\"a\", \"b\"]\n"
        + "l += [\"c\"]            # l == [\"a\", \"b\", \"c\"]</pre>"
        + "List elements have to be of the same type, <code>[1, 2, \"c\"]</code> results in an "
        + "error. Lists - just like everything - are immutable, therefore <code>l[1] = \"a\""
        + "</code> is not supported.")
public abstract class SkylarkList implements Iterable<Object> {

  private final boolean tuple;
  private final Class<?> genericType;

  private SkylarkList(boolean tuple, Class<?> genericType) {
    this.tuple = tuple;
    this.genericType = genericType;
  }

  /**
   * The size of the list.
   */
  public abstract int size();

  /**
   * Returns true if the list is empty.
   */
  public abstract boolean isEmpty();

  /**
   * Returns the i-th element of the list.
   */
  public abstract Object get(int i);

  /**
   * Returns true if this list is a tuple.
   */
  public boolean isTuple() {
    return tuple;
  }

  @VisibleForTesting
  public Class<?> getGenericType() {
    return genericType;
  }

  @Override
  public String toString() {
    return toList().toString();
  }

  // TODO(bazel-team): we should be very careful using this method. Check and remove
  // auto conversions on the Java-Skylark interface if possible.
  /**
   * Converts this Skylark list to a Java list.
   */
  public abstract List<?> toList();

  @SuppressWarnings("unchecked")
  public <T> Iterable<T> to(Class<T> type) {
    Preconditions.checkArgument(this == EMPTY_LIST || type.isAssignableFrom(genericType));
    return (Iterable<T>) this;
  }

  private static final class EmptySkylarkList extends SkylarkList {
    private EmptySkylarkList(boolean tuple) {
      super(tuple, Object.class);
    }

    @Override
    public Iterator<Object> iterator() {
      return ImmutableList.of().iterator();
    }

    @Override
    public int size() {
      return 0;
    }

    @Override
    public boolean isEmpty() {
      return true;
    }

    @Override
    public Object get(int i) {
      throw new UnsupportedOperationException();
    }

    @Override
    public List<?> toList() {
      return isTuple() ? ImmutableList.of() : Lists.newArrayList();
    }

    @Override
    public String toString() {
      return "[]";
    }
  }

  /**
   * An empty Skylark list.
   */
  public static final SkylarkList EMPTY_LIST = new EmptySkylarkList(false);

  private static final class SimpleSkylarkList extends SkylarkList {
    private final ImmutableList<Object> list;

    private SimpleSkylarkList(ImmutableList<Object> list, boolean tuple, Class<?> genericType) {
      super(tuple, genericType);
      this.list = Preconditions.checkNotNull(list);
    }

    @Override
    public Iterator<Object> iterator() {
      return list.iterator();
    }

    @Override
    public int size() {
      return list.size();
    }

    @Override
    public boolean isEmpty() {
      return list.isEmpty();
    }

    @Override
    public Object get(int i) {
      return list.get(i);
    }

    @Override
    public List<?> toList() {
      return isTuple() ? list : Lists.newArrayList(list);
    }

    @Override
    public String toString() {
      return list.toString();
    }

    @Override
    public int hashCode() {
      return list.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof SimpleSkylarkList)) {
        return false;
      }
      SimpleSkylarkList other = (SimpleSkylarkList) obj;
      return other.list.equals(this.list);
    }
  }

  /**
   * A Skylark list to support lazy iteration (i.e. we only call iterator on the object this
   * list masks when it's absolutely necessary). This is useful if iteration is expensive
   * (e.g. NestedSet-s). Size(), get() and isEmpty() are expensive operations but
   * concatenation is quick.
   */
  private static final class LazySkylarkList extends SkylarkList {
    private final Iterable<Object> iterable;
    private ImmutableList<Object> list = null;

    private LazySkylarkList(Iterable<Object> iterable, boolean tuple, Class<?> genericType) {
      super(tuple, genericType);
      this.iterable = Preconditions.checkNotNull(iterable);
    }

    @Override
    public Iterator<Object> iterator() {
      return iterable.iterator();
    }

    @Override
    public int size() {
      return Iterables.size(iterable);
    }

    @Override
    public boolean isEmpty() {
      return Iterables.isEmpty(iterable);
    }

    @Override
    public Object get(int i) {
      return getList().get(i);
    }

    @Override
    public List<?> toList() {
      return getList();
    }

    private ImmutableList<Object> getList() {
      if (list == null) {
        list = ImmutableList.copyOf(iterable);
      }
      return list;
    }
  }

  /**
   * A Skylark list to support quick concatenation of lists. Concatenation is O(1),
   * size(), isEmpty() is O(n), get() is O(h).
   */
  private static final class ConcatenatedSkylarkList extends SkylarkList {
    private final SkylarkList left;
    private final SkylarkList right;

    private ConcatenatedSkylarkList(
        SkylarkList left, SkylarkList right, boolean tuple, Class<?> genericType) {
      super(tuple, genericType);
      this.left = Preconditions.checkNotNull(left);
      this.right = Preconditions.checkNotNull(right);
    }

    @Override
    public Iterator<Object> iterator() {
      return Iterables.concat(left, right).iterator();
    }

    @Override
    public int size() {
      // We shouldn't evaluate the size function until it's necessary, because it can be expensive
      // for lazy lists (e.g. lists containing a NestedSet).
      // TODO(bazel-team): make this class more clever to store the size and empty parameters
      // for every non-LazySkylarkList member.
      return left.size() + right.size();
    }

    @Override
    public boolean isEmpty() {
      return left.isEmpty() && right.isEmpty();
    }

    @Override
    public Object get(int i) {
      int leftSize = left.size();
      if (i < leftSize) {
        return left.get(i);
      } else {
        return right.get(i - leftSize);
      }
    }

    @Override
    public List<?> toList() {
      return ImmutableList.<Object>builder().addAll(left).addAll(right).build();
    }
  }

  /**
   * Returns a Skylark list containing elements without a type check. Only use if all elements
   * are of the same type.
   */
  public static SkylarkList list(Collection<?> elements, Class<?> genericType) {
    if (elements.isEmpty()) {
      return EMPTY_LIST;
    }
    return new SimpleSkylarkList(ImmutableList.copyOf(elements), false, genericType);
  }

  /**
   * Returns a Skylark list containing elements without a type check and without creating
   * an immutable copy. Therefore the iterable containing elements must be immutable
   * (which is not checked here so callers must be extra careful). This way
   * it's possibly to create a SkylarkList without requesting the original iterator. This
   * can be useful for nested set - list conversions.
   */
  @SuppressWarnings("unchecked")
  public static SkylarkList lazyList(Iterable<?> elements, Class<?> genericType) {
    return new LazySkylarkList((Iterable<Object>) elements, false, genericType);
  }

  /**
   * Returns a Skylark list containing elements. Performs type check and throws an exception
   * in case the list contains elements of different type.
   */
  public static SkylarkList list(Collection<?> elements, Location loc) throws EvalException {
    if (elements.isEmpty()) {
      return EMPTY_LIST;
    }
    return new SimpleSkylarkList(
        ImmutableList.copyOf(elements), false, getGenericType(elements, loc));
  }

  private static Class<?> getGenericType(Collection<?> elements, Location loc)
      throws EvalException {
    Class<?> genericType = elements.iterator().next().getClass();
    for (Object element : elements) {
      Class<?> type = element.getClass();
      if (!EvalUtils.getSkylarkType(genericType).equals(EvalUtils.getSkylarkType(type))) {
        throw new EvalException(loc, String.format(
            "Incompatible types in list: found a %s but the first element is a %s",
            EvalUtils.getDataTypeNameFromClass(type),
            EvalUtils.getDataTypeNameFromClass(genericType)));
      }
    }
    return genericType;
  }

  /**
   * Returns a Skylark list created from Skylark lists left and right. Throws an exception
   * if they are not of the same generic type.
   */
  public static SkylarkList concat(SkylarkList left, SkylarkList right, Location loc)
      throws EvalException {
    if (left.isTuple() != right.isTuple()) {
      throw new EvalException(loc, "cannot concatenate lists and tuples");
    }
    if (left == EMPTY_LIST) {
      return right;
    }
    if (right == EMPTY_LIST) {
      return left;
    }
    if (!left.genericType.equals(right.genericType)) {
      throw new EvalException(loc, String.format("cannot concatenate list of %s with list of %s",
          EvalUtils.getDataTypeNameFromClass(left.genericType),
          EvalUtils.getDataTypeNameFromClass(right.genericType)));
    }
    return new ConcatenatedSkylarkList(left, right, left.isTuple(), left.genericType);
  }

  /**
   * Returns a Skylark tuple containing elements.
   */
  public static SkylarkList tuple(List<?> elements) {
    // Tuple elements do not have to have the same type.
    return new SimpleSkylarkList(ImmutableList.copyOf(elements), true, Object.class);
  }
}