blob: d331b586a72379acafff3657fb5f6bc57376299c [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.sling.scripting.sightly.impl.compiler.optimization;
import java.util.HashMap;
import java.util.Map;
import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.Identifier;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.MapLiteral;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.NullLiteral;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.PropertyAccess;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.StringConstant;
import org.apache.sling.scripting.sightly.compiler.commands.Command;
import org.apache.sling.scripting.sightly.compiler.commands.CommandStream;
import org.apache.sling.scripting.sightly.compiler.commands.VariableBinding;
import org.apache.sling.scripting.sightly.impl.compiler.util.expression.NodeTransformer;
import org.apache.sling.scripting.sightly.impl.compiler.util.stream.EmitterVisitor;
import org.apache.sling.scripting.sightly.impl.compiler.PushStream;
import org.apache.sling.scripting.sightly.impl.compiler.util.stream.Streams;
import org.apache.sling.scripting.sightly.impl.compiler.visitor.TrackingVisitor;
/**
* This optimization handles variables initialized to map literals. It initially assigns the values in the map to variables and wherever
* the map literal is accessed, the property access is replaced with the value variable, thus removing the need for a map lookup.
*/
public final class SyntheticMapRemoval extends TrackingVisitor<MapLiteral> implements EmitterVisitor {
public static final StreamTransformer TRANSFORMER = new StreamTransformer() {
@Override
public CommandStream transform(CommandStream inStream) {
return Streams.map(inStream, new SyntheticMapRemoval());
}
};
private static final String VARIABLE_MARKER = "_field$_";
private final PushStream outputStream = new PushStream();
private final NodeTransformer transformer = new PropertyAccessTransformer();
private SyntheticMapRemoval() {
}
@Override
public void visit(VariableBinding.Start variableBindingStart) {
ExpressionNode node = variableBindingStart.getExpression();
String variable = variableBindingStart.getVariableName();
ExpressionNode transformed = transform(node);
if (transformed instanceof MapLiteral) {
MapLiteral newLiteral = overrideMap(variable, (MapLiteral) transformed);
tracker.pushVariable(variable, newLiteral);
transformed = newLiteral;
} else {
tracker.pushVariable(variable, null);
}
outputStream.write(new VariableBinding.Start(variable, transformed));
}
@Override
public void visit(VariableBinding.End variableBindingEnd) {
Map.Entry<String, MapLiteral> entry = tracker.peek();
super.visit(variableBindingEnd);
MapLiteral literal = entry.getValue();
if (literal != null) {
//need to un-bind all the introduced variables
for (int i = 0; i < literal.getMap().size(); i++) {
outputStream.write(VariableBinding.END);
}
}
}
private ExpressionNode transform(ExpressionNode node) {
return transformer.transform(node);
}
private MapLiteral overrideMap(String variableName, MapLiteral mapLiteral) {
Map<String, ExpressionNode> newLiteral = new HashMap<>();
for (Map.Entry<String, ExpressionNode> entry : mapLiteral.getMap().entrySet()) {
String property = entry.getKey();
ExpressionNode valueNode = entry.getValue();
String valueVariable = valueVariableName(variableName, property);
newLiteral.put(property, new Identifier(valueVariable));
outputStream.write(new VariableBinding.Start(valueVariable, valueNode));
}
return new MapLiteral(newLiteral);
}
private String valueVariableName(String variableName, String propertyName) {
return variableName + VARIABLE_MARKER + propertyName;
}
@Override
protected MapLiteral assignDefault(Command command) {
return null;
}
@Override
public void onCommand(Command command) {
outputStream.write(command);
}
@Override
public PushStream getOutputStream() {
return outputStream;
}
private class PropertyAccessTransformer extends NodeTransformer {
@Override
public ExpressionNode evaluate(PropertyAccess propertyAccess) {
ExpressionNode target = propertyAccess.getTarget();
String variable = extractIdentifier(target);
if (variable != null) {
MapLiteral literal = tracker.get(variable);
if (literal != null) {
String property = extractProperty(propertyAccess.getProperty());
if (property != null) {
ExpressionNode replacementNode = literal.getValue(property);
if (replacementNode == null) {
replacementNode = NullLiteral.INSTANCE;
}
return replacementNode;
}
}
}
return super.evaluate(propertyAccess);
}
private String extractProperty(ExpressionNode expressionNode) {
if (expressionNode instanceof StringConstant) {
return ((StringConstant) expressionNode).getText();
}
return null;
}
private String extractIdentifier(ExpressionNode node) {
if (node instanceof Identifier) {
return ((Identifier) node).getName();
}
return null;
}
}
}