// 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. * *

Think of this as similar to {@link Collections#unmodifiableList}, etc., except that it's an * extendable class rather than a method. * *

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}. * *

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 before 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.) * *

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 extends BaseMutableWrapper implements Collection { @Override protected abstract Collection 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 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[] 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 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 extends MutableCollection implements List { @Override protected abstract List 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 listIterator() { return Collections.unmodifiableList(getContentsUnsafe()).listIterator(); } @Override public ListIterator listIterator(int index) { return Collections.unmodifiableList(getContentsUnsafe()).listIterator(index); } @Override public List 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 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 extends BaseMutableWrapper implements Map { @Override protected abstract Map 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> 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 keySet() { return Collections.unmodifiableMap(getContentsUnsafe()).keySet(); } @Override public int size() { return getContentsUnsafe().size(); } @Override public Collection 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 map) { throw new UnsupportedOperationException(); } @Deprecated @Override public final V remove(Object key) { throw new UnsupportedOperationException(); } } }