// Copyright 2014 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
package com.google.devtools.build.skyframe;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* Encapsulation of data stored by {@link NodeEntry} when the value has finished building.
*
*
This is intended only for use in alternative {@code MemoizingEvaluator} implementations.
*/
public abstract class ValueWithMetadata implements SkyValue {
protected final SkyValue value;
private static final NestedSet NO_EVENTS =
NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private static final NestedSet NO_POSTS =
NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private ValueWithMetadata(SkyValue value) {
this.value = value;
}
/**
* Builds a value entry value that has an error (and no value value).
*
* This is intended only for use in alternative {@code MemoizingEvaluator} implementations.
*/
public static ValueWithMetadata error(
ErrorInfo errorInfo,
NestedSet transitiveEvents,
NestedSet transitivePostables) {
return (ValueWithMetadata) normal(null, errorInfo, transitiveEvents, transitivePostables);
}
/**
* Builds a SkyValue that has a value, and possibly an error, and possibly events/postables. If it
* has only a value, returns just the value in order to save memory.
*
* This is public only for use in alternative {@code MemoizingEvaluator} implementations.
*/
public static SkyValue normal(
@Nullable SkyValue value,
@Nullable ErrorInfo errorInfo,
NestedSet transitiveEvents,
NestedSet transitivePostables) {
Preconditions.checkState(value != null || errorInfo != null,
"Value and error cannot both be null");
if (errorInfo == null) {
return (transitiveEvents.isEmpty() && transitivePostables.isEmpty())
? value
: ValueWithEvents.createValueWithEvents(value, transitiveEvents, transitivePostables);
}
return new ErrorInfoValue(errorInfo, value, transitiveEvents, transitivePostables);
}
@Nullable SkyValue getValue() {
return value;
}
@Nullable
abstract ErrorInfo getErrorInfo();
public abstract NestedSet getTransitiveEvents();
public abstract NestedSet getTransitivePostables();
/** Implementation of {@link ValueWithMetadata} for the value case. */
@VisibleForTesting
public static class ValueWithEvents extends ValueWithMetadata {
private final NestedSet transitiveEvents;
private final NestedSet transitivePostables;
private ValueWithEvents(
SkyValue value,
NestedSet transitiveEvents,
NestedSet transitivePostables) {
super(Preconditions.checkNotNull(value));
this.transitiveEvents = Preconditions.checkNotNull(transitiveEvents);
this.transitivePostables = Preconditions.checkNotNull(transitivePostables);
}
private static ValueWithEvents createValueWithEvents(
SkyValue value,
NestedSet transitiveEvents,
NestedSet transitivePostables) {
if (value instanceof NotComparableSkyValue) {
return new NotComparableValueWithEvents(value, transitiveEvents, transitivePostables);
} else {
return new ValueWithEvents(value, transitiveEvents, transitivePostables);
}
}
@Nullable
@Override
ErrorInfo getErrorInfo() { return null; }
@Override
public NestedSet getTransitiveEvents() { return transitiveEvents; }
@Override
public NestedSet getTransitivePostables() {
return transitivePostables;
}
/**
* We override equals so that if the same value is written to a {@link NodeEntry} twice, it can
* verify that the two values are equal, and avoid incrementing its version.
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ValueWithEvents that = (ValueWithEvents) o;
// Shallow equals is a middle ground between using default equals, which might miss
// nested sets with the same elements, and deep equality checking, which would be expensive.
// All three choices are sound, since shallow equals and default equals are more
// conservative than deep equals. Using shallow equals means that we may unnecessarily
// consider some values unequal that are actually equal, but this is still a net win over
// deep equals.
return value.equals(that.value)
&& transitiveEvents.shallowEquals(that.transitiveEvents)
&& transitivePostables.shallowEquals(that.transitivePostables);
}
@Override
public int hashCode() {
return 31 * value.hashCode()
+ transitiveEvents.shallowHashCode()
+ 3 * transitivePostables.shallowHashCode();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("value", value)
.add("transitiveEvents size", Iterables.size(transitiveEvents))
.add("transitivePostables size", Iterables.size(transitivePostables))
.toString();
}
}
private static final class NotComparableValueWithEvents extends ValueWithEvents
implements NotComparableSkyValue {
private NotComparableValueWithEvents(
SkyValue value,
NestedSet transitiveEvents,
NestedSet transitivePostables) {
super(value, transitiveEvents, transitivePostables);
}
}
/**
* Implementation of {@link ValueWithMetadata} for the error case.
*
* ErorInfo does not override equals(), so it may as well be marked NotComparableSkyValue.
*/
private static final class ErrorInfoValue extends ValueWithMetadata
implements NotComparableSkyValue {
private final ErrorInfo errorInfo;
private final NestedSet transitiveEvents;
private final NestedSet transitivePostables;
public ErrorInfoValue(
ErrorInfo errorInfo,
@Nullable SkyValue value,
NestedSet transitiveEvents,
NestedSet transitivePostables) {
super(value);
this.errorInfo = Preconditions.checkNotNull(errorInfo);
this.transitiveEvents = Preconditions.checkNotNull(transitiveEvents);
this.transitivePostables = Preconditions.checkNotNull(transitivePostables);
}
@Nullable
@Override
ErrorInfo getErrorInfo() { return errorInfo; }
@Override
public NestedSet getTransitiveEvents() { return transitiveEvents; }
@Override
public NestedSet getTransitivePostables() {
return transitivePostables;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ErrorInfoValue that = (ErrorInfoValue) o;
// Shallow equals is a middle ground between using default equals, which might miss
// nested sets with the same elements, and deep equality checking, which would be expensive.
// All three choices are sound, since shallow equals and default equals are more
// conservative than deep equals. Using shallow equals means that we may unnecessarily
// consider some values unequal that are actually equal, but this is still a net win over
// deep equals.
return Objects.equals(this.value, that.value)
&& Objects.equals(this.errorInfo, that.errorInfo)
&& transitiveEvents.shallowEquals(that.transitiveEvents)
&& transitivePostables.shallowEquals(that.transitivePostables);
}
@Override
public int hashCode() {
return 31 * Objects.hash(value, errorInfo)
+ transitiveEvents.shallowHashCode()
+ 3 * transitivePostables.shallowHashCode();
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
if (value != null) {
result.append("Value: ").append(value);
}
if (errorInfo != null) {
if (result.length() > 0) {
result.append("; ");
}
result.append("Error: ").append(errorInfo);
}
return result.toString();
}
}
public static SkyValue justValue(SkyValue value) {
if (value instanceof ValueWithMetadata) {
return ((ValueWithMetadata) value).getValue();
}
return value;
}
public static ValueWithMetadata wrapWithMetadata(SkyValue value) {
if (value instanceof ValueWithMetadata) {
return (ValueWithMetadata) value;
}
return ValueWithEvents.createValueWithEvents(value, NO_EVENTS, NO_POSTS);
}
@Nullable
public static ErrorInfo getMaybeErrorInfo(SkyValue value) {
if (value.getClass() == ErrorInfoValue.class) {
return ((ValueWithMetadata) value).getErrorInfo();
}
return null;
}
static NestedSet getEvents(SkyValue value) {
if (value instanceof ValueWithMetadata) {
return ((ValueWithMetadata) value).getTransitiveEvents();
}
return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
}
static NestedSet getPosts(SkyValue value) {
if (value instanceof ValueWithMetadata) {
return ((ValueWithMetadata) value).getTransitivePostables();
}
return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
}
}