blob: 00a00cffbd6aa134869069361fb9ec6df9f1ebb8 [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.ArrayList;
import java.util.List;
import java.util.Stack;
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.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 removes variables which are bound but never used in the command stream.
*/
public final class UnusedVariableRemoval extends TrackingVisitor<UnusedVariableRemoval.VariableActivity> implements EmitterVisitor {
public static final StreamTransformer TRANSFORMER = new StreamTransformer() {
@Override
public CommandStream transform(CommandStream inStream) {
return Streams.map(inStream, new UnusedVariableRemoval());
}
};
private final PushStream outputStream = new PushStream();
private final Stack<List<Command>> storedCommandsStack = new Stack<>();
private UnusedVariableRemoval() {
}
@Override
public PushStream getOutputStream() {
return outputStream;
}
@Override
public void visit(VariableBinding.Start variableBindingStart) {
//push a new buffer where we will store the following commands
//these commands will be emitted only if this variable will be used in
//it's scope
storedCommandsStack.push(new ArrayList<Command>());
//start tracking the variable
tracker.pushVariable(variableBindingStart.getVariableName(), new VariableActivity(variableBindingStart));
}
@Override
public void visit(VariableBinding.End variableBindingEnd) {
// Get the activity of the exiting variable
VariableActivity variableActivity = tracker.peek().getValue();
tracker.popVariable();
boolean emitBindingEnd = true;
if (variableActivity != null) {
//this was a tracked variable. Popping all the commands
//which were delayed for this variable
List<Command> commands = storedCommandsStack.pop();
//if the variable binding is emitted than this binding
//end must be emitted as well
emitBindingEnd = variableActivity.isUsed();
if (variableActivity.isUsed()) {
VariableBinding.Start variableBindingStart = variableActivity.getCommand();
//variable was used. we can let it pass through
emit(variableBindingStart);
//register the usage of all the variables that appear in the bound expression
registerUsage(variableBindingStart);
}
//write all the delayed commands
for (Command command : commands) {
emit(command);
}
}
if (emitBindingEnd) {
emit(variableBindingEnd);
}
}
@Override
protected VariableActivity assignDefault(Command command) {
return null;
}
@Override
protected void onCommand(Command command) {
registerUsage(command);
emit(command);
}
/**
* Emit the current command. If the command is delayed by
* a variable tracking process, than add it to the top command list
* @param command a stream command
*/
private void emit(Command command) {
if (storedCommandsStack.isEmpty()) {
outputStream.write(command);
} else {
List<Command> list = storedCommandsStack.peek();
list.add(command);
}
}
/**
* Extract all the variables in this command and mark them
* as used
* @param command - a stream command
*/
private void registerUsage(Command command) {
List<String> usedVariables = CommandVariableUsage.extractVariables(command);
for (String usedVariable : usedVariables) {
VariableActivity activity = tracker.get(usedVariable);
if (activity != null) {
activity.markUsed();
}
}
}
/**
* Track the activity of a variable binding
*/
static class VariableActivity {
private boolean used;
private VariableBinding.Start command;
VariableActivity(VariableBinding.Start command) {
this.command = command;
}
public void markUsed() {
used = true;
}
public boolean isUsed() {
return used;
}
public VariableBinding.Start getCommand() {
return command;
}
}
}