/* * Copyright 2015 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.jbcsrc.runtime; import static com.google.common.base.Preconditions.checkState; import com.google.template.soy.data.SanitizedContent.ContentKind; import com.google.template.soy.data.SoyValue; import com.google.template.soy.data.SoyValueProvider; import com.google.template.soy.data.UnsafeSanitizedContentOrdainer; import com.google.template.soy.data.restricted.SoyString; import com.google.template.soy.data.restricted.StringData; import com.google.template.soy.jbcsrc.api.AdvisingAppendable; import com.google.template.soy.jbcsrc.api.AdvisingStringBuilder; import com.google.template.soy.jbcsrc.api.RenderResult; import java.io.IOException; import javax.annotation.Nullable; /** * A special implementation of {@link SoyValueProvider} to use as a shared base class for the {@code * jbcsrc} implementations of the generated {@code LetContentNode} and {@code CallParamContentNode} * implementations. */ public abstract class DetachableContentProvider implements SoyValueProvider { @Nullable private final ContentKind contentKind; // Will be either a SanitizedContent or a StringData. private SoyString resolvedValue; // Will be either an AdvisingStringBuilder or a TeeAdvisingAppendable depending on whether we are // being resolved via 'status()' or via 'renderAndResolve()' private AdvisingAppendable builder; protected DetachableContentProvider(@Nullable ContentKind contentKind) { this.contentKind = contentKind; } @Override public final SoyValue resolve() { SoyString local = resolvedValue; checkState(local != null, "called resolve() before status() returned ready."); checkState( local != TombstoneValue.INSTANCE, "called resolve() after calling renderAndResolve with isLast == true"); return local; } @Override public final RenderResult status() { if (resolvedValue != null) { return RenderResult.done(); } AdvisingStringBuilder currentBuilder = (AdvisingStringBuilder) builder; if (currentBuilder == null) { builder = currentBuilder = new AdvisingStringBuilder(); } return doRenderIntoBufferingAppendable(currentBuilder); } @Override public RenderResult renderAndResolve(AdvisingAppendable appendable, boolean isLast) throws IOException { SoyValue value = resolvedValue; if (value != null) { value.render(appendable); return RenderResult.done(); } if (isLast) { RenderResult result = doRender(appendable); if (result.isDone()) { resolvedValue = TombstoneValue.INSTANCE; } return result; } TeeAdvisingAppendable currentBuilder = (TeeAdvisingAppendable) builder; if (currentBuilder == null) { builder = currentBuilder = new TeeAdvisingAppendable(appendable); } return doRenderIntoBufferingAppendable(currentBuilder); } private RenderResult doRenderIntoBufferingAppendable(AdvisingAppendable target) { RenderResult result = doRender(target); if (result.isDone()) { if (contentKind != null) { resolvedValue = UnsafeSanitizedContentOrdainer.ordainAsSafe(target.toString(), contentKind); } else { resolvedValue = StringData.forValue(target.toString()); } } return result; } /** Overridden by generated subclasses to implement lazy detachable resolution. */ protected abstract RenderResult doRender(AdvisingAppendable appendable); /** * An {@link AdvisingAppendable} that forwards to a delegate appendable but also saves all the * same forwarded content into a buffer. * * <p>See: <a href="http://en.wikipedia.org/wiki/Tee_%28command%29">Tee command for the unix * command on which this is based. */ private static final class TeeAdvisingAppendable implements AdvisingAppendable { final StringBuilder buffer = new StringBuilder(); final AdvisingAppendable delegate; TeeAdvisingAppendable(AdvisingAppendable delegate) { this.delegate = delegate; } @Override public AdvisingAppendable append(CharSequence csq) throws IOException { delegate.append(csq); buffer.append(csq); return this; } @Override public AdvisingAppendable append(CharSequence csq, int start, int end) throws IOException { delegate.append(csq, start, end); buffer.append(csq, start, end); return this; } @Override public AdvisingAppendable append(char c) throws IOException { delegate.append(c); buffer.append(c); return this; } @Override public boolean softLimitReached() { return delegate.softLimitReached(); } @Override public String toString() { return buffer.toString(); } } }