// Copyright 2018 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.skyframe; import com.google.common.base.Preconditions; /** * For use when the {@link #argument} of the {@link SkyKey} cannot be a {@link SkyKey} itself, * either because it is a type like List or because it is already a different {@link SkyKey}. * Provides convenient boilerplate. */ public abstract class AbstractSkyKey implements SkyKey { // Visible for serialization. protected final T arg; /** * Cache the hash code for this object. It might be expensive to compute. It is transient because * argument's hash code might not be stable across JVM instances. */ private transient int hashCode; protected AbstractSkyKey(T arg) { this.arg = Preconditions.checkNotNull(arg); } @Override public final int hashCode() { // We use the hash code caching strategy employed by java.lang.String. There are three subtle // things going on here: // // (1) We use a value of 0 to indicate that the hash code hasn't been computed and cached yet. // Yes, this means that if the hash code is really 0 then we will "recompute" it each time. But // this isn't a problem in practice since a hash code of 0 should be rare. // // (2) Since we have no synchronization, multiple threads can race here thinking there are the // first one to compute and cache the hash code. // // (3) Moreover, since 'hashCode' is non-volatile, the cached hash code value written from one // thread may not be visible by another. // // All three of these issues are benign from a correctness perspective; in the end we have no // overhead from synchronization, at the cost of potentially computing the hash code more than // once. int h = hashCode; if (h == 0) { h = computeHashCode(); hashCode = h; } return h; } @Override public final T argument() { return arg; } private int computeHashCode() { return 31 * functionName().hashCode() + arg.hashCode(); } @Override public final boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } AbstractSkyKey that = (AbstractSkyKey) obj; return this.functionName().equals(that.functionName()) && this.arg.equals(that.arg); } @Override public String toString() { return functionName() + ":" + arg; } }