/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*
* Contributor(s):
*
* Portions Copyrighted 2007 Sun Microsystems, Inc.
*/
package org.netbeans.modules.ruby.hints.introduce;
import org.netbeans.modules.ruby.ParseTreeVisitor;
import org.netbeans.modules.ruby.ParseTreeWalker;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jrubyparser.ast.ArgsNode;
import org.jrubyparser.ast.ListNode;
import org.jrubyparser.ast.MultipleAsgnNode;
import org.jrubyparser.ast.Node;
import org.jrubyparser.ast.NodeType;
import org.jrubyparser.ast.INameNode;
/**
* This visitor computes the set of input and output variables required by
* a code block for extract method.
* In particular, it tracks the local variable assignments inside the method,
* and checks which are used outside of the method (which would make it an
* output variable) and similarly, which variables are used inside the method
* before getting assigned (which would make it an input variable).
* @author Tor Norbye
*/
class InputOutputVarFinder implements ParseTreeVisitor {
//private enum When { BEFORE, DURING, AFTER };
private static final int WHEN_BEFORE = 0;
private static final int WHEN_DURING = 1;
private static final int WHEN_AFTER = 2;
private final Node startNode;
private final Node endNode;
private final List<Node> applicableBlocks;
private int when = WHEN_BEFORE;
private int ifs;
private Node currentBlock;
private final List<Node> blockStack = new ArrayList<Node>(); // JDK16: Use Deque
private Map<Node,UsageScope> blockScopes = new HashMap<Node,UsageScope>();
private UsageScope methodScope = new UsageScope(null);
private UsageScope blockScope;
/** The node ranges are inclusive */
InputOutputVarFinder(Node startNode, Node endNode, List<Node> applicableBlocks) {
this.startNode = startNode;
this.endNode = endNode;
this.applicableBlocks = applicableBlocks;
}
public Set<String> getInputVars() {
UsageScope scope = methodScope;
for (UsageScope s : blockScopes.values()) {
if (s.block != null && !applicableBlocks.contains(s.block)) {
continue;
}
scope.merge(s);
}
Set<String> inputs = new HashSet<String>(scope.readDuring);
// But not read before
inputs.removeAll(scope.writtenBeforeReadDuring);
// Also need to pass in any variables I'm modifying that are read after
Set<String> outputs = new HashSet<String>(scope.writtenDuring);
outputs.retainAll(scope.readAfter);
Set<String> extraOutputs = new HashSet<String>(scope.writtenBefore);
extraOutputs.retainAll(outputs);
// unless they are written before read
extraOutputs.removeAll(scope.writtenBeforeReadDuring);
inputs.addAll(extraOutputs);
return inputs;
}
public Set<String> getOutputVars() {
UsageScope scope = methodScope;
for (UsageScope s : blockScopes.values()) {
if (s.block != null && !applicableBlocks.contains(s.block)) {
continue;
}
scope.merge(s);
}
Set<String> outputs = new HashSet<String>(scope.writtenDuring);
outputs.retainAll(scope.readAfter);
return outputs;
}
public boolean visit(Node node) {
if (node == startNode) {
when = WHEN_DURING;
}
switch (node.getNodeType()) {
case ARGSNODE: {
assert when == WHEN_BEFORE; // Is this true when I extract a whole method? I can't do that, right?
// TODO - use AstUtilities.getDefArgs here - but avoid hitting them twice!
//List<String> parameters = AstUtilities.getDefArgs(def, true);
// However, I've gotta find the parameter nodes themselves too!
ArgsNode an = (ArgsNode)node;
if (an.getRequiredCount() > 0) {
List<Node> args = an.childNodes();
for (Node arg : args) {
if (arg instanceof ListNode) {
List<Node> args2 = arg.childNodes();
for (Node arg2 : args2) {
if (arg2.getNodeType() == NodeType.ARGUMENTNODE) {
methodScope.write(((INameNode)arg2).getName());
} else if (arg2.getNodeType() == NodeType.LOCALASGNNODE) {
methodScope.write(((INameNode)arg2).getName());
}
}
}
}
}
// Rest args
if (an.getRest() != null) {
String name = an.getRest().getName();
methodScope.write(name);
}
// Block args
if (an.getBlock() != null) {
String name = an.getBlock().getName();
methodScope.write(name);
}
// Skip argsnode since we've already processed it
return true;
}
case ITERNODE: {
blockStack.add(node);
currentBlock = node;
blockScope = new UsageScope(currentBlock);
blockScopes.put(currentBlock, blockScope);
if (when == WHEN_DURING) {
applicableBlocks.add(node);
}
break;
}
case DEFNNODE:
case DEFSNODE:
case CLASSNODE:
case SCLASSNODE:
case MODULENODE:
// We're probably extracting from within the top level of a file or class;
// don't look into methods
return when != WHEN_BEFORE;
case DVARNODE: {
String name = ((INameNode)node).getName();
blockScope.read(name);
break;
}
case LOCALVARNODE: {
String name = ((INameNode)node).getName();
methodScope.read(name);
break;
}
case MULTIPLEASGNNODE: {
// I need to visit the right-hand-side children nodes of this assignment first to ensure that
// in this:
// x,y=x+1,y+1
// properly sees that "x" is read before it is written.
MultipleAsgnNode multiple = (MultipleAsgnNode)node;
if (multiple.getValueNode() != null) {
new ParseTreeWalker(this).walk(multiple.getValueNode());
}
break;
}
case WHENNODE:
case IFNODE: {
ifs++;
}
}
return false;
}
public boolean unvisit(Node node) {
switch (node.getNodeType()) {
case ITERNODE: {
blockStack.remove(blockStack.size()-1);
currentBlock = blockStack.size() > 0 ? blockStack.get(blockStack.size()-1) : null;
if (currentBlock != null) {
blockScope = blockScopes.get(currentBlock);
assert blockScope != null;
}
break;
}
// I must process assignments AFTER I've processed the children since
// x = x + 1
// should be processed as a read before a write even though I encounter the
// LocalAsgnNode before its child LocalVarNode
case LOCALASGNNODE: {
String name = ((INameNode)node).getName();
methodScope.write(name);
break;
}
case DASGNNODE: {
String name = ((INameNode)node).getName();
blockScope.write(name);
break;
}
case WHENNODE:
case IFNODE: {
ifs--;
}
}
if (node == endNode) {
when = WHEN_AFTER;
}
return false;
}
private class UsageScope {
UsageScope(Node block) {
this.block = block;
}
private void read(String name) {
if (when == WHEN_DURING) {
if (!writtenBeforeReadDuring.contains(name)) {
readDuring.add(name);
}
} else if (when == WHEN_AFTER) {
// I don't want a reassignment of the variable before it's been
// read to count as a usage of the result from the fragment
if (!writtenAfter.contains(name)) {
readAfter.add(name);
}
}
}
private void write(String name) {
if (when == WHEN_BEFORE) {
writtenBefore.add(name);
} else if (when == WHEN_DURING) {
writtenDuring.add(name);
if (ifs == 0 && !readDuring.contains(name)) {
writtenBeforeReadDuring.add(name);
}
} else if (when == WHEN_AFTER) {
if (ifs == 0 && !readAfter.contains(name)) {
writtenAfter.add(name);
}
}
}
private void merge(UsageScope other) {
writtenBefore.addAll(other.writtenBefore);
readDuring.addAll(other.readDuring);
writtenDuring.addAll(other.writtenDuring);
writtenBeforeReadDuring.addAll(other.writtenBeforeReadDuring);
writtenAfter.addAll(other.writtenAfter);
readAfter.addAll(other.readAfter);
}
/** Block, or null if it's the local method */
private Node block;
/** Variables that exist in scope before the code fragment */
private final Set<String> writtenBefore = new HashSet<String>();
/** Variables that are read during the code fragment */
private final Set<String> readDuring = new HashSet<String>(); // rename readBeforeWrittenDuring
/** Variables that are written to during the code fragment */
private final Set<String> writtenDuring = new HashSet<String>();
/** Variables that are written to during the code fragment */
private final Set<String> writtenBeforeReadDuring = new HashSet<String>();
/** Variables that are written PRIOR TO A READ OF THE SAME VAR after the code fragment */
private final Set<String> writtenAfter = new HashSet<String>(); // rename writtenBeforeReadAfter
/** Variables that are read (prior to a write) after the code fragment */
private final Set<String> readAfter = new HashSet<String>(); // rename readBeforeWrittenAfter
}
}