package com.aptana.rdt.internal.parser.warnings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jruby.ast.CallNode;
import org.jruby.ast.DVarNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.FCallNode;
import org.jruby.ast.Node;
import org.jruby.ast.SelfNode;
import org.jruby.ast.VCallNode;
import org.jruby.lexer.yacc.ISourcePosition;
import org.rubypeople.rdt.core.parser.warnings.RubyLintVisitor;
import org.rubypeople.rdt.internal.core.util.ASTUtil;
import com.aptana.rdt.AptanaRDTPlugin;
import com.aptana.rdt.IProblem;
public class FeatureEnvy extends RubyLintVisitor
{
private static final int DEFAULT_MIN_REFERENCES_FOR_REPORT = 2;
private static final String SELF = "self";
private HashMap<String, List<ISourcePosition>> references = new HashMap<String, List<ISourcePosition>>();
private boolean recordReferences = false;
private int minReferences;
public FeatureEnvy(String src)
{
this(AptanaRDTPlugin.getDefault().getOptions(), src);
}
public FeatureEnvy(Map<String, String> options, String src)
{
super(options, src);
minReferences = getInt(AptanaRDTPlugin.COMPILER_PB_MIN_REFERENCES_FOR_ENVY, DEFAULT_MIN_REFERENCES_FOR_REPORT);
}
private int getInt(String key, int defaultValue)
{
try
{
return Integer.parseInt((String) fOptions.get(key));
}
catch (NumberFormatException e)
{
return defaultValue;
}
}
@Override
protected int getProblemID()
{
return IProblem.FeatureEnvy;
}
@Override
protected String getOptionKey()
{
return AptanaRDTPlugin.COMPILER_PB_FEATURE_ENVY;
}
@Override
public Object visitDefnNode(DefnNode visited)
{
enterMethod();
return super.visitDefnNode(visited);
}
private void enterMethod()
{
recordReferences = true;
}
@Override
public Object visitDefsNode(DefsNode visited)
{
enterMethod();
return super.visitDefsNode(visited);
}
@Override
public void exitDefnNode(DefnNode visited)
{
exitMethod();
super.exitDefnNode(visited);
}
@Override
public void exitDefsNode(DefsNode visited)
{
exitMethod();
super.exitDefsNode(visited);
}
private void exitMethod()
{
List<ISourcePosition> enviousReferences = getEnviousReferences();
for (ISourcePosition pos : enviousReferences)
{
createProblem(pos,
"Feature envy: More method calls made to object than self. Consider moving method to object.");
}
recordReferences = false;
references.clear();
}
private List<ISourcePosition> getEnviousReferences()
{
if (references.isEmpty()) // no references
return Collections.emptyList();
if (references.size() == 1 && references.containsKey(SELF)) // only references to self
return Collections.emptyList();
Collection<List<ISourcePosition>> listOfPositions = references.values();
int max = Math.max(0, minReferences);
for (List<ISourcePosition> list : listOfPositions)
{
if (list.size() > max)
max = list.size();
}
List<ISourcePosition> envious = new ArrayList<ISourcePosition>();
for (Map.Entry<String, List<ISourcePosition>> entry : references.entrySet())
{
if (entry.getValue().size() == max)
{
if (entry.getKey().equals(SELF))
{
envious.clear();
break;
}
else
{
envious.add(entry.getValue().get(0));
}
}
}
return envious;
}
@Override
public Object visitCallNode(CallNode visited)
{
if (!visited.getName().equals("new"))
{
Node receiver = visited.getReceiverNode();
if (receiver instanceof SelfNode)
{
recordReference(SELF, receiver.getPosition());
}
else if (receiver instanceof DVarNode)
{
return super.visitCallNode(visited); // don't count calls made on dynamic vars because blocks are
// special!
}
// else if (receiver instanceof ConstNode)
// {
// // Calling a method on a constant usually means we're calling a type singleton method
// }
else
{
String expr = ASTUtil.getNameReflectively(receiver);
if (expr == null)
expr = ASTUtil.stringRepresentation(receiver);
recordReference(expr, receiver.getPosition());
}
}
return super.visitCallNode(visited);
}
@Override
public Object visitFCallNode(FCallNode visited)
{
recordReference(SELF, visited.getPosition());
return super.visitFCallNode(visited);
}
@Override
public Object visitVCallNode(VCallNode visited)
{
recordReference(SELF, visited.getPosition());
return super.visitVCallNode(visited);
}
private void recordReference(String name, ISourcePosition pos)
{
if (!recordReferences)
return;
List<ISourcePosition> value = references.get(name);
if (value == null)
value = new ArrayList<ISourcePosition>();
value.add(pos);
references.put(name, value);
}
}