/*
* 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);
}
}