blob: 3532bfb0860187077802dc05ac534c7d0cdbc583 [file] [log] [blame]
// 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();
}
}
}