/* * Copyright 2015 Martin Kouba * * 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 org.trimou.engine.context; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.trimou.engine.config.Configuration; import org.trimou.engine.config.EngineConfigurationKey; import org.trimou.engine.parser.Template; import org.trimou.engine.resolver.EnhancedResolver; import org.trimou.engine.resolver.EnhancedResolver.Hint; import org.trimou.engine.resolver.Placeholder; import org.trimou.engine.resolver.Resolver; import org.trimou.engine.segment.Segment; import org.trimou.exception.MustacheException; import org.trimou.exception.MustacheProblem; /** * A default implementation. * * @author Martin Kouba */ final class DefaultExecutionContext implements ExecutionContext { private final DefaultExecutionContext parent; private final Configuration configuration; protected final Object contextObject; protected final Template templateInvocation; protected final int invocationLimitCounter; protected final Map<String, Segment> definingSections; protected final Resolver[] resolvers; /** * * @param parent * @param configuration * @param contextObject * @param templateInvocation * @param templateInvocations * @param invocationLimitCounter * @param definingSections * @param resolvers */ DefaultExecutionContext(DefaultExecutionContext parent, Configuration configuration, Object contextObject, Template templateInvocation, int invocationLimitCounter, Map<String, Segment> definingSections, Resolver[] resolvers) { this.parent = parent; this.configuration = configuration; this.contextObject = contextObject; this.templateInvocation = templateInvocation; this.invocationLimitCounter = invocationLimitCounter; this.definingSections = definingSections; this.resolvers = resolvers; } @Override public ValueWrapper getValue(String key, String[] keyParts, AtomicReference<Hint> hintRef) { ValueWrapper value = new ValueWrapper(key); Object lastValue; if (keyParts == null || keyParts.length == 0) { Iterator<String> parts = configuration.getKeySplitter().split(key); lastValue = resolveLeadingContextObject(parts.next(), value, hintRef); if (lastValue == null) { // Leading context object not found - miss return value; } while (parts.hasNext()) { value.processNextPart(); lastValue = resolve(lastValue, parts.next(), value, false); if (lastValue == null) { // Not found - miss return value; } } } else { lastValue = resolveLeadingContextObject(keyParts[0], value, hintRef); if (lastValue == null) { // Leading context object not found - miss return value; } if (keyParts.length > 1) { for (int i = 1; i < keyParts.length; i++) { value.processNextPart(); lastValue = resolve(lastValue, keyParts[i], value, false); if (lastValue == null) { // Not found - miss return value; } } } } if (!Placeholder.NULL.equals(lastValue)) { value.set(lastValue); } return value; } @Override public ValueWrapper getValue(String key) { return getValue(key, null, null); } @Override public ExecutionContext setContextObject(Object object) { return new DefaultExecutionContext(this, configuration, object, null, invocationLimitCounter, null, resolvers); } @Override public Object getFirstContextObject() { if (contextObject != null) { return contextObject; } if (parent != null) { return parent.getFirstContextObject(); } return null; } @Override public ExecutionContext setTemplateInvocation(Template template) { if (invocationLimitCounter < 0 && getTemplateInvocations(template) > configuration .getIntegerPropertyValue(EngineConfigurationKey.TEMPLATE_RECURSIVE_INVOCATION_LIMIT)) { throw new MustacheException( MustacheProblem.RENDER_TEMPLATE_INVOCATION_RECURSIVE_LIMIT_EXCEEDED, "Recursive invocation limit exceeded [limit: %s, level: %s, template: %s]", configuration .getIntegerPropertyValue(EngineConfigurationKey.TEMPLATE_RECURSIVE_INVOCATION_LIMIT), invocationLimitCounter, templateInvocation); } return new DefaultExecutionContext(this, configuration, null, template, invocationLimitCounter - 1, null, resolvers); } @Override public ExecutionContext setDefiningSections(Iterable<Segment> segments) { Map<String, Segment> definingSections = null; for (Segment segment : segments) { if (getDefiningSection(segment.getText()) == null) { if (definingSections == null) { definingSections = new HashMap<>(); } definingSections.put(segment.getText(), segment); } } return new DefaultExecutionContext(this, configuration, null, null, invocationLimitCounter, definingSections, resolvers); } @Override public Segment getDefiningSection(String name) { Segment section = null; if (definingSections != null) { section = definingSections.get(name); } if (section == null && parent != null) { section = parent.getDefiningSection(name); } return section; } @Override public ExecutionContext getParent() { return parent; } private int getTemplateInvocations(Template template) { int invocations = 0; if (templateInvocation != null && templateInvocation.equals(template)) { invocations++; } if (parent != null) { invocations += parent.getTemplateInvocations(template); } return invocations; } /** * Resolve the leading context object (the first part of the key). E.g. * <code>foo</code> in <code>{{foo.bar.name}}</code> may identify a property * of some context object on the stack (passed data, section iteration, * nested context, ...), or some context and data unrelated object (e.g. CDI * bean). * * @param name * @param value * The value wrapper - ResolutionContext * @param hintRef * @return the resolved leading context object * @see Hint */ private Object resolveLeadingContextObject(String name, ValueWrapper value, AtomicReference<Hint> hintRef) { Object leading = resolveContextObject(name, value, hintRef); if (leading == null) { // Leading context object not found - try to resolve context // unrelated objects (JNDI lookup, CDI, etc.) Hint hint = hintRef != null ? hintRef.get() : null; if (hint != null) { leading = hint.resolve(null, name, value); } if (leading == null) { leading = resolve(null, name, value, hint == null && hintRef != null); } } return leading; } private Object resolveContextObject(String name, ValueWrapper value, AtomicReference<Hint> hintRef) { Object leading = null; if (contextObject != null) { Hint hint = hintRef != null ? hintRef.get() : null; if (hint != null) { leading = hint.resolve(contextObject, name, value); } if (leading == null) { leading = resolve(contextObject, name, value, hint == null && hintRef != null); } } if (leading == null && parent != null) { leading = parent.resolveContextObject(name, value, hintRef); } return leading; } private Object resolve(Object contextObject, String name, ValueWrapper value, boolean createHint) { Object resolved = null; for (final Resolver resolver : resolvers) { resolved = resolver.resolve(contextObject, name, value); if (resolved != null) { if (createHint) { // Initialize a new hint if possible if (resolver instanceof EnhancedResolver) { value.setHint(((EnhancedResolver) resolver).createHint( contextObject, name, value)); } } break; } } return resolved; } }