/*
* Copyright 2013 Google Inc.
*
* 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.template.soy.data;
import com.google.common.base.Preconditions;
import com.google.template.soy.jbcsrc.api.AdvisingAppendable;
import com.google.template.soy.jbcsrc.api.RenderResult;
import com.google.template.soy.jbcsrc.api.RenderResult.Type;
import java.io.IOException;
import javax.annotation.Nullable;
/**
* A SoyValueProvider that lazily computes and caches its value.
*
* <p>SoyAbstractCachingValueProvider is thread-safe, but in a race condition, may compute its value
* twice.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
*/
public abstract class SoyAbstractCachingValueProvider implements SoyValueProvider {
/**
* A mechanism to plug in assertions on the computed value that will be run the first time the
* value is {@link SoyAbstractCachingValueProvider#compute() computed}.
*/
public abstract static class ValueAssertion {
private ValueAssertion next;
public abstract void check(SoyValue value);
}
/**
* The resolved value.
*
* <p>This will be set to non-null the first time this is resolved. Note that SoyValue must not be
* null. This is volatile to indicate it will be tested and set atomically across threads.
*/
private volatile SoyValue resolvedValue = null;
// We thread a simple linked list through this field to eliminate the cost of allocating a
// collection
@Nullable private ValueAssertion valueAssertion;
@Override
public final SoyValue resolve() {
// NOTE: If this is used across threads, the worst that will happen is two different providers
// will be constructed, and an arbitrary one will win. Since setting a volatile reference is
// atomic, however, this is thread-safe. We keep a local cache here to avoid doing a memory
// read more than once in the cached case.
SoyValue localResolvedValue = resolvedValue;
if (localResolvedValue == null) {
localResolvedValue = compute();
for (ValueAssertion curr = valueAssertion; curr != null; curr = curr.next) {
curr.check(localResolvedValue);
}
resolvedValue = localResolvedValue;
valueAssertion = null;
}
return localResolvedValue;
}
@Override
public RenderResult renderAndResolve(AdvisingAppendable appendable, boolean isLast)
throws IOException {
// Gives a reasonable default implementation, if subclasses can do better they can override.
RenderResult result = status();
if (result.type() == Type.DONE) {
resolve().render(appendable);
}
return result;
}
@Override
public int hashCode() {
throw new UnsupportedOperationException(
"SoyAbstractCachingValueProvider is unsuitable for use as a hash key.");
}
/** Returns {@code true} if the caching provider has already been calculated. */
public final boolean isComputed() {
return resolvedValue != null;
}
/** Registers a {@link ValueAssertion} callback with this caching provider. */
public void addValueAssertion(ValueAssertion assertion) {
Preconditions.checkState(
resolvedValue == null,
"ValueAssertions should only be registered if the value is not yet computed.");
assertion.next = valueAssertion;
valueAssertion = assertion;
}
/** Implemented by subclasses to do the heavy-lifting for resolving. */
protected abstract SoyValue compute();
}