/**
* Copyright 2008 Anders Hessellund
*
* Licensed 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.
*
* $Id: AvailableMapKeys.java,v 1.1 2008/01/17 18:48:19 hessellund Exp $
*/
package org.ofbiz.plugin.analysis;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.ofbiz.plugin.Plugin;
class AvailableMapKeys extends
MonotonicDataFlowFramework<Pair<String, String>> {
/**
* computes available map keys for each statement in a single method
* Lattice element: (map x key)
* @param cfg The control flow graph of the method
*/
AvailableMapKeys(ControlFlowGraph cfg) {
super(cfg, true, false);
}
private Set<Pair<String, String>> universe = new HashSet<Pair<String, String>>();
@Override void computeFixPoint() {
// compute universe of possible ( map x key ) combinations
computeUniverse(new HashSet<Statement>(), cfg.start);
// compute fixpoint with all EXIT(B) set to universe
computeFixPoint(new HashSet<Pair<String, String>>(),universe);
}
@Override String getAnalysisName() {
return "Avaliable Map Keys";
}
@Override String toString(Pair<String, String> e) {
return "<"+e.first+","+e.second+">";
}
/** returns null if the statement is not a ExpressionStatement
* with an embedded method invocation */
private MethodInvocation getMethodInvocation(Statement stmt) {
if (stmt instanceof ExpressionStatement) {
ExpressionStatement es = (ExpressionStatement) stmt;
Expression expr = es.getExpression();
if (expr instanceof MethodInvocation) {
return (MethodInvocation) expr;
}
}
// only handles variable declaration statements with a single fragment. TODO: handle more fragments
if (stmt instanceof VariableDeclarationStatement) {
VariableDeclarationStatement vds = (VariableDeclarationStatement) stmt;
if (vds.fragments().size()==1) {
VariableDeclarationFragment vdf = (VariableDeclarationFragment) vds.fragments().get(0);
Expression expr = vdf.getInitializer();
if (expr instanceof MethodInvocation) {
return (MethodInvocation) expr;
}
}
}
return null;
}
/** returns a pair (map x key) from a java.util.Map.put()-operation */
private Pair<String,String> getPutOperation(MethodInvocation mi) {
Expression expr = mi.getExpression();
if (expr==null) return null;
ITypeBinding binding = expr.resolveTypeBinding();
if ("java.util.Map".equals(binding.getQualifiedName()) &&
"put".equals(mi.getName().getIdentifier())) {
if(!(expr instanceof SimpleName)) {
// special case, screensAtt.getContext().put("formStringRenderer", foFormRenderer);
return null;
}
SimpleName variable = (SimpleName) expr;
Object key = mi.arguments().get(0);
// ordinary strings
if (key instanceof StringLiteral) {
String keyContent = key.toString().replace("\"", "");
return new Pair<String,String>(variable.getIdentifier(),keyContent);
// constants
} else if (key instanceof QualifiedName) {
QualifiedName name = (QualifiedName) key;
Object obj = name.resolveConstantExpressionValue();
if (obj!=null && obj instanceof String) {
return new Pair<String,String>(variable.getIdentifier(),obj.toString());
}
}
}
return null;
}
private Map<String, Set<Statement>> namesUsedInInterproceduralCalls = new HashMap<String, Set<Statement>>();
boolean isNameUsedInInterproceduralCall(String name) {
return namesUsedInInterproceduralCalls.containsKey( name );
}
Set<Statement> getInterproceduralCalls( String name ) {
assert namesUsedInInterproceduralCalls.containsKey( name );
return namesUsedInInterproceduralCalls.get( name );
}
private void addNameToInterproceduralCallSet(String name, Statement location) {
Set<Statement> set;
if (namesUsedInInterproceduralCalls.containsKey( name )) {
set = namesUsedInInterproceduralCalls.get( name );
} else {
set = new HashSet<Statement>();
namesUsedInInterproceduralCalls.put( name , set );
}
set.add( location );
}
@Override protected Set<Pair<String, String>> gen(Set<Pair<String, String>> set, Statement stmt) {
Set<Pair<String, String>> result = new HashSet<Pair<String, String>>();
MethodInvocation mi = getMethodInvocation(stmt);
// is it a method invocation?
if (mi!=null) {
// is it a java.util.Map.put()-operation?
Pair<String,String> pair = getPutOperation(mi);
if (pair!= null) {
result.add(pair);
return result;
}
// check for interprocedural calls
String clazz = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName();
if (!"org.ofbiz.base.util.UtilMisc".equals(clazz)
&& !"org.ofbiz.service.ServiceUtil".equals(clazz)
&& !"javolution.util.FastMap".equals(clazz)){
// check if any of the arguments is a java.util.Map
for(Object obj : mi.arguments()) {
if (obj instanceof SimpleName) {
SimpleName arg = (SimpleName) obj;
ITypeBinding argType = arg.resolveTypeBinding();
IBinding argBinding = arg.resolveBinding();
if ("java.util.Map".equals(argType.getQualifiedName())) {
addNameToInterproceduralCallSet(argBinding.getName(), stmt);
}
}
}
// handles Map out = doSomething(..)
if (stmt instanceof VariableDeclarationStatement) {
VariableDeclarationStatement vds = (VariableDeclarationStatement) stmt;
//TODO: handle VariableDeclarationStatement with more than one fragment
if (vds.fragments().size()==1) {
VariableDeclarationFragment vdf =
(VariableDeclarationFragment) vds.fragments().get(0);
IVariableBinding var = vdf.resolveBinding();
if ("java.util.Map".equals(var.getType().getQualifiedName())) {
addNameToInterproceduralCallSet(var.getName(), stmt);
}
}
}
// handles out.putAll(..)
if ("putAll".equals(mi.getName().getIdentifier())) {
Expression expr = mi.getExpression();
if (expr instanceof SimpleName) {
if ("java.util.Map".equals(expr.resolveTypeBinding().getQualifiedName())) {
String name = ((SimpleName) expr).getIdentifier();
addNameToInterproceduralCallSet(name, stmt);
}
}
}
}
}
// handles assignment: "out = dispatcher.runSynch(...)
if (stmt instanceof ExpressionStatement) {
ExpressionStatement exprStmt = (ExpressionStatement) stmt;
if (exprStmt.getExpression() instanceof Assignment) {
Assignment assignment = (Assignment) exprStmt.getExpression();
String assignmentType = assignment.resolveTypeBinding().getQualifiedName();
if ("java.util.Map".equals(assignmentType)) {
//TODO: this is to simple. Could be something else than a simplename
if (assignment.getLeftHandSide() instanceof SimpleName) {
SimpleName sn = (SimpleName) assignment.getLeftHandSide();
addNameToInterproceduralCallSet(sn.getIdentifier(), stmt);
}
}
}
}
return result;
}
@Override protected Set<Pair<String, String>> kill(Set<Pair<String, String>> set, Statement stmt) {
Set<Pair<String, String>> result = new HashSet<Pair<String, String>>();
// is it a method invocation?
MethodInvocation mi = getMethodInvocation(stmt);
if (mi!=null) {
Expression expr = mi.getExpression();
if (expr==null) return result;
ITypeBinding binding = expr.resolveTypeBinding();
// clear-calls removes all keys
if ("java.util.Map".equals(binding.getQualifiedName()) &&
"clear".equals(mi.getName().getIdentifier())) {
assert universe!=null : "universe must be initialized!";
if (expr instanceof SimpleName) {
String var = ((SimpleName) expr).getIdentifier();
for(Pair<String,String> pair : universe) {
if (pair.first.equals(var)) {
result.add(pair);
}
}
}
}
// remove-calls removes a single key
if ("java.util.Map".equals(binding.getQualifiedName()) &&
"remove".equals(mi.getName().getIdentifier())) {
SimpleName variable = (SimpleName) expr;
Object key = mi.arguments().get(0);
if (key instanceof StringLiteral) {
String keyContent = key.toString().replace("\"", "");
result.add(new Pair<String,String>(variable.getIdentifier(),keyContent));
}
}
}
// "out = null;" or "out = new HashMap();
if (stmt instanceof ExpressionStatement) {
ExpressionStatement exprStmt = (ExpressionStatement) stmt;
Expression expr = exprStmt.getExpression();
if (expr instanceof Assignment) {
Assignment assignment = (Assignment) expr;
Expression lhs = assignment.getLeftHandSide();
ITypeBinding lhsBinding = lhs.resolveTypeBinding();
if ("java.util.Map".equals(lhsBinding.getQualifiedName())) {
String var = ((SimpleName) lhs).getIdentifier();
Expression rhs = assignment.getRightHandSide();
if (rhs instanceof SimpleName &&
((SimpleName) rhs).getIdentifier().equals(var)) {
// out = out => ignore
} else {
// out = ...
for(Pair<String,String> pair : universe) {
if (pair.first.equals(var)) {
result.add(pair);
}
}
}
}
}
}
return result;
}
private void computeUniverse(Set<Statement> seen, Statement stmt) {
MethodInvocation mi = getMethodInvocation(stmt);
if (mi!=null) {
Pair<String,String> pair = getPutOperation(mi);
if (pair!=null) {
universe.add(pair);
}
}
seen.add(stmt);
// TODO: ignore blocks from cfg?
if (stmt instanceof Block) return;
// recursion
try {
for(Statement successor : cfg.successors.get(stmt)) {
if (!seen.contains(successor))
computeUniverse(seen, successor);
}
} catch (RuntimeException e) {
Plugin.logError("ERROR in "+Util.getFirstLine(stmt), e);
throw new RuntimeException(e);
}
}
}