/* * Copyright 2003-2015 JetBrains s.r.o. * * 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 jetbrains.mps.generator.impl; import jetbrains.mps.generator.runtime.TemplateContext; import jetbrains.mps.generator.runtime.TemplateExecutionEnvironment; import jetbrains.mps.lang.pattern.GeneratedMatchingPattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.annotations.Immutable; import org.jetbrains.mps.openapi.model.SNode; import java.util.Collections; import java.util.Iterator; import java.util.Map; @Immutable public class DefaultTemplateContext implements TemplateContext { private final TemplateExecutionEnvironment myEnv; private final DefaultTemplateContext myParent; private final SNode myInputNode; private final String myInputName; private final GeneratedMatchingPattern myPattern; private final Map<String, Object> myVars; public DefaultTemplateContext(@NotNull TemplateExecutionEnvironment env, @Nullable SNode inputNode, @Nullable String inputName) { myEnv = env; myParent = null; myInputName = inputName; myInputNode = inputNode; myPattern = null; myVars = null; } /** * Creates a new context with given named input node, while preserving reference to originating context. */ private DefaultTemplateContext(@NotNull DefaultTemplateContext parent, String inputName, SNode inputNode) { this(parent, inputName, inputNode, null, null); } /** * Creates a new context for template declaration. */ private DefaultTemplateContext(@NotNull DefaultTemplateContext parent, Map<String, Object> variables) { this(parent, null, parent.getInput(), null, variables); } private DefaultTemplateContext(DefaultTemplateContext parent, String inputName, SNode inputNode, GeneratedMatchingPattern pattern, Map<String,Object> vars) { myParent = parent; myEnv = parent == null ? null : parent.myEnv; myInputName = inputName; myInputNode = inputNode; myPattern = pattern; myVars = vars; } @NotNull @Override public TemplateExecutionEnvironment getEnvironment() { return myEnv; } public DefaultTemplateContext getParent() { return myParent; } @Override public SNode getInput() { return myInputNode; } @Override public String getInputName() { return myInputName; } @Override public Object getPatternVariable(String id) { for (DefaultTemplateContext current = this; current != null; current = current.myParent) { if (current.myPattern != null) { return current.myPattern.getFieldValue(id); } } return null; } @Override public Object getVariable(String name) { for (DefaultTemplateContext current = this; current != null; current = current.myParent) { if (current.myVars != null && current.myVars.containsKey(name)) { return current.myVars.get(name); } } return null; } @Override public boolean hasVariable(String name) { for (DefaultTemplateContext current = this; current != null; current = current.myParent) { if (current.myVars != null && current.myVars.containsKey(name)) { return true; } } return false; } @Override public SNode getNamedInput(String name) { for (DefaultTemplateContext current = this; current != null; current = current.myParent) { if (current.myInputName != null && current.myInputName.equals(name)) { return current.myInputNode; } } return null; } @Override public Iterable<SNode> getInputHistory() { return new Iterable<SNode>() { @Override public Iterator<SNode> iterator() { return new Iterator<SNode>() { SNode previous; DefaultTemplateContext current; { current = DefaultTemplateContext.this; while (current != null && current.myInputNode == null) { current = current.myParent; } previous = current != null ? current.myInputNode : null; } @Override public boolean hasNext() { skipOdd(); return current != null; } @Override public SNode next() { skipOdd(); if (current != null) { previous = current.myInputNode; current = current.myParent; return previous; } return null; } private void skipOdd() { while (current != null && (current.myInputNode == null || current.myInputNode == previous)) { current = current.myParent; } } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } @Override public TemplateContext subContext(String inputName, SNode inputNode) { // this method seems to be flawed. inputNode different from present one gives new context with // updated input name, while different inputName gives updated context only when it's != null, so that // calling #subContext(null, probablyNewInputNode) gives no confidence whether we've *cleared* mappingLabel or not. if (inputNode == getInput() && (inputName == null || inputName.equals(getInputName()))) { return this; } return new DefaultTemplateContext(this, inputName, inputNode); } @Override public TemplateContext subContext(String inputName) { if (inputName == null || inputName.equals(getInputName())) { return this; } return new DefaultTemplateContext(this, inputName, getInput()); } @Override public TemplateContext subContext(Map<String, Object> variables) { if (variables == null || variables.isEmpty()) { return this; } return new DefaultTemplateContext(this, variables); } @Override public TemplateContext withVariable(String name, Object value) { assert name != null; return new DefaultTemplateContext(this, getInputName(), getInput(), null, Collections.singletonMap(name, value)); } @Override public TemplateContext subContext(GeneratedMatchingPattern pattern) { return new DefaultTemplateContext(this, null, getInput(), pattern, null); } @Override public TemplateContext subContext() { if (getInputName() == null) { return this; } return new DefaultTemplateContext(this, null, getInput()); } @Override public TemplateContext subContext(SNode newInputNode) { if (newInputNode == getInput()) { return this; } return new DefaultTemplateContext(this, getInputName(), newInputNode); } }