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