| // Copyright 2016 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.syntax; | |
| import com.google.common.collect.Iterators; | |
| import com.google.devtools.build.lib.events.Location; | |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; | |
| import com.google.devtools.build.lib.syntax.Mutability.Freezable; | |
| import com.google.devtools.build.lib.syntax.Mutability.MutabilityException; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.ListIterator; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Base class for data structures that are only mutable using a proper, unfrozen {@link Mutability}. | |
| */ | |
| public abstract class SkylarkMutable implements Freezable, SkylarkValue { | |
| /** | |
| * Checks whether this object is currently mutable in the given {@link Environment}, and throws | |
| * an exception if it is not. | |
| * | |
| * @deprecated prefer {@link #checkMutable(Location, Mutability)} instead | |
| */ | |
| @Deprecated | |
| protected void checkMutable(Location loc, Environment env) throws EvalException { | |
| checkMutable(loc, env.mutability()); | |
| } | |
| /** | |
| * Checks whether this object is currently mutable using the given {@link Mutability}, and throws | |
| * an exception if it is not. | |
| * | |
| * @throws EvalException if the object is not mutable. This may be because the object (i.e., its | |
| * {@code Mutability} was frozen, or because it is temporarily locked from mutation (due to | |
| * being iterated over by a loop), or because it is associated with a different {@code | |
| * Mutability} than the one given. | |
| */ | |
| protected void checkMutable(Location loc, Mutability mutability) throws EvalException { | |
| try { | |
| Mutability.checkMutable(this, mutability); | |
| } catch (MutabilityException ex) { | |
| throw new EvalException(loc, ex); | |
| } | |
| } | |
| @Override | |
| public boolean isImmutable() { | |
| // By the Mutability invariants, if we're frozen and our Mutability is not SHALLOW_IMMUTABLE, | |
| // then all contained values are also deeply frozen. Only tuples use SHALLOW_IMMUTABLE and they | |
| // override this method anyway, so we can just do a constant-time check here. | |
| return mutability().isFrozen(); | |
| } | |
| /** | |
| * Add a new lock at {@code loc}. No effect if frozen. | |
| */ | |
| public void lock(Location loc) { | |
| mutability().lock(this, loc); | |
| } | |
| /** | |
| * Remove the lock at {@code loc}; such a lock must already exist. No effect if frozen. | |
| */ | |
| public void unlock(Location loc) { | |
| mutability().unlock(this, loc); | |
| } | |
| /** | |
| * Base class for a {@link SkylarkMutable} that implements a Java Collections Framework interface. | |
| * All of the interface's accessors should be supported, while its mutating methods must be | |
| * disallowed. | |
| * | |
| * <p>Think of this as similar to {@link Collections#unmodifiableList}, etc., except that it's an | |
| * extendable class rather than a method. | |
| * | |
| * <p>A subclass implements a specific data structure interface, say {@link List}, and refines the | |
| * return type of {@link #getContentsUnsafe} to be that interface. The subclass implements all of | |
| * the interface's accessors such that they defer to the result of {@code getContentsUnsafe}. | |
| * Accessors such as {@link Collection#iterator()} must return unmodifiable views. The subclass | |
| * implements final versions of all the interface's mutating methods such that they are marked | |
| * {@code @Deprecated} and throw {@link UnsupportedOperationException}. | |
| * | |
| * <p>A concrete subclass may provide alternative mutating methods that take in a {@link | |
| * Mutability} and validate that the mutation is allowed using {@link #checkMutable}. This | |
| * validation must occur <em>before</em> the mutation, not after, in order to ensure that a frozen | |
| * value cannot be mutated. (I.e., the fact that the check throws {@link EvalException} does not | |
| * excuse us from illegally mutating a frozen value, since {@code EvalException} is not a fatal | |
| * error.) | |
| * | |
| * <p>Subclasses need not overwrite the default methods added to some data structures in Java 8. | |
| * since these are defined in terms of the non-default methods. | |
| */ | |
| abstract static class BaseMutableWrapper extends SkylarkMutable { | |
| /** | |
| * The underlying contents, to which read access is forwarded. This object must not be modified | |
| * without first calling {@link #checkMutable}. | |
| */ | |
| protected abstract Object getContentsUnsafe(); | |
| @Override | |
| public boolean equals(Object o) { | |
| return getContentsUnsafe().equals(o); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return getContentsUnsafe().hashCode(); | |
| } | |
| } | |
| /** Base class for a {@link SkylarkMutable} that is also a {@link Collection}. */ | |
| abstract static class MutableCollection<E> extends BaseMutableWrapper implements Collection<E> { | |
| @Override | |
| protected abstract Collection<E> getContentsUnsafe(); | |
| // Reading methods of Collection, in alphabetic order. | |
| @Override | |
| public boolean contains(@Nullable Object object) { | |
| return getContentsUnsafe().contains(object); | |
| } | |
| @Override | |
| public boolean containsAll(Collection<?> collection) { | |
| return getContentsUnsafe().containsAll(collection); | |
| } | |
| @Override | |
| public boolean isEmpty() { | |
| return getContentsUnsafe().isEmpty(); | |
| } | |
| @Override | |
| public Iterator<E> iterator() { | |
| return Iterators.unmodifiableIterator(getContentsUnsafe().iterator()); | |
| } | |
| @Override | |
| public int size() { | |
| return getContentsUnsafe().size(); | |
| } | |
| // toArray() and toArray(T[]) return copies, so we don't need an unmodifiable view. | |
| @Override | |
| public Object[] toArray() { | |
| return getContentsUnsafe().toArray(); | |
| } | |
| @Override | |
| public <T> T[] toArray(T[] other) { | |
| return getContentsUnsafe().toArray(other); | |
| } | |
| // (Disallowed) writing methods of Collection, in alphabetic order. | |
| @Deprecated | |
| @Override | |
| public final boolean add(E element) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final boolean addAll(Collection<? extends E> collection) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final void clear() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final boolean remove(Object object) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final boolean removeAll(Collection<?> collection) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final boolean retainAll(Collection<?> collection) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| } | |
| /** Base class for a {@link SkylarkMutable} that is also a {@link List}. */ | |
| abstract static class BaseMutableList<E> extends MutableCollection<E> implements List<E> { | |
| @Override | |
| protected abstract List<E> getContentsUnsafe(); | |
| // Reading methods of List, in alphabetic order. | |
| @Override | |
| public E get(int i) { | |
| return getContentsUnsafe().get(i); | |
| } | |
| @Override | |
| public int indexOf(Object element) { | |
| return getContentsUnsafe().indexOf(element); | |
| } | |
| @Override | |
| public int lastIndexOf(Object element) { | |
| return getContentsUnsafe().lastIndexOf(element); | |
| } | |
| @Override | |
| public ListIterator<E> listIterator() { | |
| return Collections.unmodifiableList(getContentsUnsafe()).listIterator(); | |
| } | |
| @Override | |
| public ListIterator<E> listIterator(int index) { | |
| return Collections.unmodifiableList(getContentsUnsafe()).listIterator(index); | |
| } | |
| @Override | |
| public List<E> subList(int fromIndex, int toIndex) { | |
| return Collections.unmodifiableList(getContentsUnsafe()).subList(fromIndex, toIndex); | |
| } | |
| // (Disallowed) writing methods of List, in alphabetic order. | |
| @Deprecated | |
| @Override | |
| public final void add(int index, E element) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final boolean addAll(int index, Collection<? extends E> elements) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final E remove(int index) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final E set(int index, E element) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| } | |
| /** Base class for a {@link SkylarkMutable} that is also a {@link Map}. */ | |
| abstract static class MutableMap<K, V> extends BaseMutableWrapper implements Map<K, V> { | |
| @Override | |
| protected abstract Map<K, V> getContentsUnsafe(); | |
| // Reading methods of Map, in alphabetic order. | |
| @Override | |
| public boolean containsKey(Object key) { | |
| return getContentsUnsafe().containsKey(key); | |
| } | |
| @Override | |
| public boolean containsValue(Object value) { | |
| return getContentsUnsafe().containsValue(value); | |
| } | |
| @Override | |
| public Set<Map.Entry<K, V>> entrySet() { | |
| return Collections.unmodifiableMap(getContentsUnsafe()).entrySet(); | |
| } | |
| @Override | |
| public V get(Object key) { | |
| return getContentsUnsafe().get(key); | |
| } | |
| @Override | |
| public boolean isEmpty() { | |
| return getContentsUnsafe().isEmpty(); | |
| } | |
| @Override | |
| public Set<K> keySet() { | |
| return Collections.unmodifiableMap(getContentsUnsafe()).keySet(); | |
| } | |
| @Override | |
| public int size() { | |
| return getContentsUnsafe().size(); | |
| } | |
| @Override | |
| public Collection<V> values() { | |
| return Collections.unmodifiableMap(getContentsUnsafe()).values(); | |
| } | |
| // (Disallowed) writing methods of Map, in alphabetic order. | |
| @Deprecated | |
| @Override | |
| public final void clear() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final V put(K key, V value) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final void putAll(Map<? extends K, ? extends V> map) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Deprecated | |
| @Override | |
| public final V remove(Object key) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| } | |
| } |