/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */
package net.sourceforge.pmd.rules;

import net.sourceforge.pmd.AbstractRule;
import net.sourceforge.pmd.ast.ASTArguments;
import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.ast.ASTCompilationUnit;
import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.ast.ASTEnumDeclaration;
import net.sourceforge.pmd.ast.ASTExplicitConstructorInvocation;
import net.sourceforge.pmd.ast.ASTLiteral;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.ast.ASTName;
import net.sourceforge.pmd.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.ast.AccessNode;
import net.sourceforge.pmd.ast.Node;
import net.sourceforge.pmd.ast.SimpleNode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Searches through all methods and constructors called from constructors.  It
 * marks as dangerous any call to overridable methods from non-private
 * constructors.  It marks as dangerous any calls to dangerous private constructors
 * from non-private constructors.
 *
 * @author CL Gilbert (dnoyeb@users.sourceforge.net)
 * @todo match parameter types.  Aggressively strips off any package names.  Normal
 * compares the names as is.
 * @todo What about interface declarations which can have internal classes
 */
public final class ConstructorCallsOverridableMethod extends AbstractRule {
    /**
     * 2: method();
     * ASTPrimaryPrefix
     * ASTName			image = "method"
     * ASTPrimarySuffix
     * *ASTArguments
     * 3: a.method();
     * ASTPrimaryPrefix ->
     * ASTName			image = "a.method" ???
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * 3: this.method();
     * ASTPrimaryPrefix -> this image=null
     * ASTPrimarySuffix -> method
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * <p/>
     * super.method();
     * ASTPrimaryPrefix -> image = "method"
     * ASTPrimarySuffix -> image = null
     * ASTArguments ->
     * <p/>
     * super.a.method();
     * ASTPrimaryPrefix -> image = "a"
     * ASTPrimarySuffix -> image = "method"
     * ASTPrimarySuffix -> image = null
     * ASTArguments ->
     * <p/>
     * <p/>
     * 4: this.a.method();
     * ASTPrimaryPrefix -> image = null
     * ASTPrimarySuffix -> image = "a"
     * ASTPrimarySuffix -> image = "method"
     * ASTPrimarySuffix ->
     * ASTArguments
     * <p/>
     * 4: ClassName.this.method();
     * ASTPrimaryPrefix
     * ASTName	image = "ClassName"
     * ASTPrimarySuffix -> this image=null
     * ASTPrimarySuffix -> image = "method"
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * 5: ClassName.this.a.method();
     * ASTPrimaryPrefix
     * ASTName image = "ClassName"
     * ASTPrimarySuffix -> this image=null
     * ASTPrimarySuffix -> image="a"
     * ASTPrimarySuffix -> image="method"
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * 5: Package.ClassName.this.method();
     * ASTPrimaryPrefix
     * ASTName image ="Package.ClassName"
     * ASTPrimarySuffix -> this image=null
     * ASTPrimarySuffix -> image="method"
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * 6: Package.ClassName.this.a.method();
     * ASTPrimaryPrefix
     * ASTName image ="Package.ClassName"
     * ASTPrimarySuffix -> this image=null
     * ASTPrimarySuffix -> a
     * ASTPrimarySuffix -> method
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * 5: OuterClass.InnerClass.this.method();
     * ASTPrimaryPrefix
     * ASTName image = "OuterClass.InnerClass"
     * ASTPrimarySuffix -> this image=null
     * ASTPrimarySuffix -> method
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * 6: OuterClass.InnerClass.this.a.method();
     * ASTPrimaryPrefix
     * ASTName image = "OuterClass.InnerClass"
     * ASTPrimarySuffix -> this image=null
     * ASTPrimarySuffix -> a
     * ASTPrimarySuffix -> method
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * <p/>
     * OuterClass.InnerClass.this.a.method().method().method();
     * ASTPrimaryPrefix
     * ASTName image = "OuterClass.InnerClass"
     * ASTPrimarySuffix -> this		image=null
     * ASTPrimarySuffix -> a			image='a'
     * ASTPrimarySuffix -> method		image='method'
     * ASTPrimarySuffix -> ()			image=null
     * ASTArguments
     * ASTPrimarySuffix -> method		image='method'
     * ASTPrimarySuffix -> ()			image=null
     * ASTArguments
     * ASTPrimarySuffix -> method		image='method'
     * ASTPrimarySuffix -> ()			image=null
     * ASTArguments
     * <p/>
     * 3..n:	Class.InnerClass[0].InnerClass[n].this.method();
     * ASTPrimaryPrefix
     * ASTName image = "Class[0]..InnerClass[n]"
     * ASTPrimarySuffix -> image=null
     * ASTPrimarySuffix -> method
     * ASTPrimarySuffix -> ()
     * ASTArguments
     * <p/>
     * super.aMethod();
     * ASTPrimaryPrefix -> aMethod
     * ASTPrimarySuffix -> ()
     * <p/>
     * Evaluate right to left
     */
    private static class MethodInvocation {
        private String m_Name;
        private ASTPrimaryExpression m_Ape;
        private List m_ReferenceNames;
        private List m_QualifierNames;
        private int m_ArgumentSize;
        private boolean m_Super;

        private MethodInvocation(ASTPrimaryExpression ape, List qualifierNames, List referenceNames, String name, int argumentSize, boolean superCall) {
            m_Ape = ape;
            m_QualifierNames = qualifierNames;
            m_ReferenceNames = referenceNames;
            m_Name = name;
            m_ArgumentSize = argumentSize;
            m_Super = superCall;
        }

        public boolean isSuper() {
            return m_Super;
        }

        public String getName() {
            return m_Name;
        }

        public int getArgumentCount() {
            return m_ArgumentSize;
        }

        public List getReferenceNames() {
            return m_ReferenceNames;//new ArrayList(variableNames);
        }

        public List getQualifierNames() {
            return m_QualifierNames;
        }

        public ASTPrimaryExpression getASTPrimaryExpression() {
            return m_Ape;
        }

        public static MethodInvocation getMethod(ASTPrimaryExpression node) {
            MethodInvocation meth = null;
            int i = node.jjtGetNumChildren();
            if (i > 1) {//should always be at least 2, probably can eliminate this check
                //start at end which is guaranteed, work backwards
                Node lastNode = node.jjtGetChild(i - 1);
                if ((lastNode.jjtGetNumChildren() == 1) && (lastNode.jjtGetChild(0) instanceof ASTArguments)) { //could be ASTExpression for instance 'a[4] = 5';
                    //start putting method together
                    //					System.out.println("Putting method together now");
                    List varNames = new ArrayList();
                    List packagesAndClasses = new ArrayList(); //look in JLS for better name here;
                    String methodName = null;
                    ASTArguments args = (ASTArguments) lastNode.jjtGetChild(0);
                    int numOfArguments = args.getArgumentCount();
                    boolean superFirst = false;
                    int thisIndex = -1;

                    FIND_SUPER_OR_THIS: {
                        //search all nodes except last for 'this' or 'super'.  will be at: (node 0 | node 1 | nowhere)
                        //this is an ASTPrimarySuffix with a null image and does not have child (which will be of type ASTArguments)
                        //this is an ASTPrimaryPrefix with a null image and an ASTName that has a null image
                        //super is an ASTPrimarySuffix with a null image and does not have child (which will be of type ASTArguments)
                        //super is an ASTPrimaryPrefix with a non-null image
                        for (int x = 0; x < i - 1; x++) {
                            Node child = node.jjtGetChild(x);
                            if (child instanceof ASTPrimarySuffix) { //check suffix type match
                                ASTPrimarySuffix child2 = (ASTPrimarySuffix) child;
                                //								String name = getNameFromSuffix((ASTPrimarySuffix)child);
                                //								System.out.println("found name suffix of : " + name);
                                if (child2.getImage() == null && child2.jjtGetNumChildren() == 0) {
                                    thisIndex = x;
                                    break;
                                }
                                //could be super, could be this.  currently we cant tell difference so we miss super when
                                //XYZ.ClassName.super.method();
                                //still works though.
                            } else if (child instanceof ASTPrimaryPrefix) { //check prefix type match
                                ASTPrimaryPrefix child2 = (ASTPrimaryPrefix) child;
                                if (getNameFromPrefix(child2) == null) {
                                    if (child2.getImage() == null) {
                                        thisIndex = x;
                                        break;
                                    } else {//happens when super is used [super.method(): image = 'method']
                                        superFirst = true;
                                        thisIndex = x;
                                        //the true super is at an unusable index because super.method() has only 2 nodes [method=0,()=1]
                                        //as opposed to the 3 you might expect and which this.method() actually has. [this=0,method=1.()=2]
                                        break;
                                    }
                                }
                            }
                            //							else{
                            //								System.err.println("Bad Format error"); //throw exception, quit evaluating this compilation node
                            //							}
                        }
                    }

                    if (thisIndex != -1) {
                        //						System.out.println("Found this or super: " + thisIndex);
                        //Hack that must be removed if and when the patters of super.method() begins to logically match the rest of the patterns !!!
                        if (superFirst) { //this is when super is the first node of statement.  no qualifiers, all variables or method
                            //							System.out.println("super first");
                            FIRSTNODE:{
                                ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
                                String name = child.getImage();//special case
                                if (i == 2) { //last named node = method name
                                    methodName = name;
                                } else { //not the last named node so its only var name
                                    varNames.add(name);
                                }
                            }
                            OTHERNODES:{ //variables
                                for (int x = 1; x < i - 1; x++) {
                                    Node child = node.jjtGetChild(x);
                                    ASTPrimarySuffix ps = (ASTPrimarySuffix) child;
                                    if (ps.isArguments() == false) {
                                        String name = ((ASTPrimarySuffix) child).getImage();
                                        if (x == i - 2) {//last node
                                            methodName = name;
                                        } else {//not the last named node so its only var name
                                            varNames.add(name);
                                        }
                                    }
                                }
                            }
                        } else {//not super call
                            FIRSTNODE:{
                                if (thisIndex == 1) {//qualifiers in node 0
                                    ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
                                    String toParse = getNameFromPrefix(child);
                                    //									System.out.println("parsing for class/package names in : " + toParse);
                                    java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, ".");
                                    while (st.hasMoreTokens()) {
                                        packagesAndClasses.add(st.nextToken());
                                    }
                                }
                            }
                            OTHERNODES:{ //other methods called in this statement are grabbed here
                                //this is at 0, then no Qualifiers
                                //this is at 1, the node 0 contains qualifiers
                                for (int x = thisIndex + 1; x < i - 1; x++) {//everything after this is var name or method name
                                    ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x);
                                    if (child.isArguments() == false) { //skip the () of method calls
                                        String name = child.getImage();
                                        //										System.out.println("Found suffix: " + suffixName);
                                        if (x == i - 2) {
                                            methodName = name;
                                        } else {
                                            varNames.add(name);
                                        }
                                    }
                                }
                            }
                        }
                    } else { //if no this or super found, everything is method name or variable
                        //System.out.println("no this found:");
                        FIRSTNODE:{ //variable names are in the prefix + the first method call [a.b.c.x()]
                            ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
                            String toParse = getNameFromPrefix(child);
                            //							System.out.println("parsing for var names in : " + toParse);
                            java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, ".");
                            while (st.hasMoreTokens()) {
                                String value = st.nextToken();
                                if (!st.hasMoreTokens()) {
                                    if (i == 2) {//if this expression is 2 nodes long, then the last part of prefix is method name
                                        methodName = value;
                                    } else {
                                        varNames.add(value);
                                    }
                                } else { //variable name
                                    varNames.add(value);
                                }
                            }
                        }
                        OTHERNODES:{ //other methods called in this statement are grabbed here
                            for (int x = 1; x < i - 1; x++) {
                                ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x);
                                if (child.isArguments() == false) {
                                    String name = child.getImage();
                                    if (x == i - 2) {
                                        methodName = name;
                                    } else {
                                        varNames.add(name);
                                    }
                                }
                            }
                        }
                    }
                    meth = new MethodInvocation(node, packagesAndClasses, varNames, methodName, numOfArguments, superFirst);
                }
            }
            return meth;
        }

        public void show() {
            System.out.println("<MethodInvocation>");
            List pkg = getQualifierNames();
            System.out.println("  <Qualifiers>");
            for (Iterator it = pkg.iterator(); it.hasNext();) {
                String name = (String) it.next();
                System.out.println("    " + name);
            }
            System.out.println("  </Qualifiers>");
            System.out.println("  <Super>" + isSuper() + "</Super>");
            List vars = getReferenceNames();
            System.out.println("  <References>");
            for (Iterator it = vars.iterator(); it.hasNext();) {
                String name = (String) it.next();
                System.out.println("    " + name);
            }
            System.out.println("  </References>");
            System.out.println("  <Name>" + getName() + "</Name>");
            System.out.println("</MethodInvocation>");
        }
    }

    private static final class ConstructorInvocation {
        private ASTExplicitConstructorInvocation m_Eci;
        private String name;
        private int count = 0;

        public ConstructorInvocation(ASTExplicitConstructorInvocation eci) {
            m_Eci = eci;
            List l = new ArrayList();
            eci.findChildrenOfType(ASTArguments.class, l);
            if (l.size() > 0) {
                ASTArguments aa = (ASTArguments) l.get(0);
                count = aa.getArgumentCount();
            }
            name = eci.getImage();
        }

        public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() {
            return m_Eci;
        }

        public int getArgumentCount() {
            return count;
        }

        public String getName() {
            return name;
        }
    }

    private static final class MethodHolder {
        private ASTMethodDeclarator amd;
        private boolean dangerous;
        private String called;

        public MethodHolder(ASTMethodDeclarator amd) {
            this.amd = amd;
        }

        public void setCalledMethod(String name) {
            this.called = name;
        }

        public String getCalled() {
            return this.called;
        }

        public ASTMethodDeclarator getASTMethodDeclarator() {
            return amd;
        }

        public boolean isDangerous() {
            return dangerous;
        }

        public void setDangerous() {
            dangerous = true;
        }
    }

    private final class ConstructorHolder {
        private ASTConstructorDeclaration m_Cd;
        private boolean m_Dangerous;
        private ConstructorInvocation m_Ci;
        private boolean m_CiInitialized;

        public ConstructorHolder(ASTConstructorDeclaration cd) {
            m_Cd = cd;
        }

        public ASTConstructorDeclaration getASTConstructorDeclaration() {
            return m_Cd;
        }

        public ConstructorInvocation getCalledConstructor() {
            if (m_CiInitialized == false) {
                initCI();
            }
            return m_Ci;
        }

        public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() {
            ASTExplicitConstructorInvocation eci = null;
            if (m_CiInitialized == false) {
                initCI();
            }
            if (m_Ci != null) {
                eci = m_Ci.getASTExplicitConstructorInvocation();
            }
            return eci;
        }

        private void initCI() {
            List expressions = new ArrayList();
            m_Cd.findChildrenOfType(ASTExplicitConstructorInvocation.class, expressions); //only 1...
            if (!expressions.isEmpty()) {
                ASTExplicitConstructorInvocation eci = (ASTExplicitConstructorInvocation) expressions.get(0);
                m_Ci = new ConstructorInvocation(eci);
                //System.out.println("Const call " + eci.getImage()); //super or this???
            }
            m_CiInitialized = true;
        }

        public boolean isDangerous() {
            return m_Dangerous;
        }

        public void setDangerous(boolean dangerous) {
            m_Dangerous = dangerous;
        }
    }

    /**
     * 1 package per class. holds info for evaluating a single class.
     */
    private static class EvalPackage {
        public EvalPackage() {
        }

        public EvalPackage(String className) {
            m_ClassName = className;
            calledMethods = new ArrayList();//meths called from constructor
            allMethodsOfClass = new HashMap();
            calledConstructors = new ArrayList();//all constructors called from constructor
            allPrivateConstructorsOfClass = new HashMap();
        }

        public String m_ClassName;
        public List calledMethods;
        public Map allMethodsOfClass;

        public List calledConstructors;
        public Map allPrivateConstructorsOfClass;
    }

    private static final class NullEvalPackage extends EvalPackage {
        public NullEvalPackage() {
            m_ClassName = "";
            calledMethods = Collections.EMPTY_LIST;
            allMethodsOfClass = Collections.EMPTY_MAP;
            calledConstructors = Collections.EMPTY_LIST;
            allPrivateConstructorsOfClass = Collections.EMPTY_MAP;
        }
    }

    private static final NullEvalPackage nullEvalPackage = new NullEvalPackage();


    /**
     * 1 package per class.
     */
    private final List evalPackages = new ArrayList();//could use java.util.Stack

    private EvalPackage getCurrentEvalPackage() {
        return (EvalPackage) evalPackages.get(evalPackages.size() - 1);
    }

    /**
     * Adds and evaluation package and makes it current
     */
    private void putEvalPackage(EvalPackage ep) {
        evalPackages.add(ep);
    }

    private void removeCurrentEvalPackage() {
        evalPackages.remove(evalPackages.size() - 1);
    }

    private void clearEvalPackages() {
        evalPackages.clear();
    }

    /**
     * This check must be evaluated independelty for each class.  Inner classses
     * get their own EvalPackage in order to perform independent evaluation.
     */
    private Object visitClassDec(ASTClassOrInterfaceDeclaration node, Object data) {
        String className = node.getImage();
        if (!node.isFinal() && !node.isStatic()) {
            putEvalPackage(new EvalPackage(className));
        } else {
            putEvalPackage(nullEvalPackage);
        }
        //store any errors caught from other passes.
        super.visit((ASTClassOrInterfaceDeclaration) node, data);

        //skip this class if it has no evaluation package
        if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {
            //evaluate danger of all methods in class
            while (evaluateDangerOfMethods(getCurrentEvalPackage().allMethodsOfClass)) {
            }
            //evaluate danger of constructors
            evaluateDangerOfConstructors1(getCurrentEvalPackage().allPrivateConstructorsOfClass, getCurrentEvalPackage().allMethodsOfClass.keySet());
            while (evaluateDangerOfConstructors2(getCurrentEvalPackage().allPrivateConstructorsOfClass)) {
            }

            //get each method called on this object from a non-private constructor, if its dangerous flag it
            for (Iterator it = getCurrentEvalPackage().calledMethods.iterator(); it.hasNext();) {
                MethodInvocation meth = (MethodInvocation) it.next();
                //check against each dangerous method in class
                for (Iterator it2 = getCurrentEvalPackage().allMethodsOfClass.keySet().iterator(); it2.hasNext();) {
                    MethodHolder h = (MethodHolder) it2.next();
                    if (h.isDangerous()) {
                        String methName = h.getASTMethodDeclarator().getImage();
                        int count = h.getASTMethodDeclarator().getParameterCount();
                        if (meth.getName().equals(methName) && meth.getArgumentCount() == count) {
                            addViolation(data, meth.getASTPrimaryExpression(), "method '" + h.getCalled() + "'");
                        }
                    }
                }
            }
            //get each unsafe private constructor, and check if its called from any non private constructors
            for (Iterator privConstIter = getCurrentEvalPackage().allPrivateConstructorsOfClass.keySet().iterator(); privConstIter.hasNext();) {
                ConstructorHolder ch = (ConstructorHolder) privConstIter.next();
                if (ch.isDangerous()) { //if its dangerous check if its called from any non-private constructors
                    //System.out.println("visitClassDec Evaluating dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params");
                    int paramCount = ch.getASTConstructorDeclaration().getParameterCount();
                    for (Iterator calledConstIter = getCurrentEvalPackage().calledConstructors.iterator(); calledConstIter.hasNext();) {
                        ConstructorInvocation ci = (ConstructorInvocation) calledConstIter.next();
                        if (ci.getArgumentCount() == paramCount) {
                            //match name  super / this !?
                            addViolation(data, ci.getASTExplicitConstructorInvocation(), "constructor");
                        }
                    }
                }
            }
        }
        //finished evaluating this class, move up a level
        removeCurrentEvalPackage();
        return data;
    }

    /**
     * Check the methods called on this class by each of the methods on this
     * class.  If a method calls an unsafe method, mark the calling method as
     * unsafe.  This changes the list of unsafe methods which necessitates
     * another pass.  Keep passing until you make a clean pass in which no
     * methods are changed to unsafe.
     * For speed it is possible to limit the number of passes.
     * <p/>
     * Impossible to tell type of arguments to method, so forget method matching
     * on types.  just use name and num of arguments.  will be some false hits,
     * but oh well.
     *
     * @todo investigate limiting the number of passes through config.
     */
    private boolean evaluateDangerOfMethods(Map classMethodMap) {
        //check each method if it calls overridable method
        boolean found = false;
        for (Iterator methodsIter = classMethodMap.keySet().iterator(); methodsIter.hasNext();) {
            MethodHolder h = (MethodHolder) methodsIter.next();
            List calledMeths = (List) classMethodMap.get(h);
            for (Iterator calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && (h.isDangerous() == false);) {
                //if this method matches one of our dangerous methods, mark it dangerous
                MethodInvocation meth = (MethodInvocation) calledMethsIter.next();
                //System.out.println("Called meth is " + meth);
                for (Iterator innerMethsIter = classMethodMap.keySet().iterator(); innerMethsIter.hasNext();) { //need to skip self here h == h3
                    MethodHolder h3 = (MethodHolder) innerMethsIter.next();
                    if (h3.isDangerous()) {
                        String matchMethodName = h3.getASTMethodDeclarator().getImage();
                        int matchMethodParamCount = h3.getASTMethodDeclarator().getParameterCount();
                        //System.out.println("matching " + matchMethodName + " to " + meth.getName());
                        if (matchMethodName.equals(meth.getName()) && matchMethodParamCount == meth.getArgumentCount()) {
                            h.setDangerous();
                            h.setCalledMethod(matchMethodName);
                            found = true;
                            break;
                        }
                    }
                }
            }
        }
        return found;
    }

    /**
     * marks constructors dangerous if they call any dangerous methods
     * Requires only a single pass as methods are already marked
     *
     * @todo optimize by having methods already evaluated somehow!?
     */
    private void evaluateDangerOfConstructors1(Map classConstructorMap, Set evaluatedMethods) {
        //check each constructor in the class
        for (Iterator constIter = classConstructorMap.keySet().iterator(); constIter.hasNext();) {
            ConstructorHolder ch = (ConstructorHolder) constIter.next();
            if (!ch.isDangerous()) {//if its not dangerous then evaluate if it should be
                //if it calls dangerous method mark it as dangerous
                List calledMeths = (List) classConstructorMap.get(ch);
                //check each method it calls
                for (Iterator calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && !ch.isDangerous();) {//but thee are diff objects which represent same thing but were never evaluated, they need reevaluation
                    MethodInvocation meth = (MethodInvocation) calledMethsIter.next();//CCE
                    String methName = meth.getName();
                    int methArgCount = meth.getArgumentCount();
                    //check each of the already evaluated methods: need to optimize this out
                    for (Iterator evaldMethsIter = evaluatedMethods.iterator(); evaldMethsIter.hasNext();) {
                        MethodHolder h = (MethodHolder) evaldMethsIter.next();
                        if (h.isDangerous()) {
                            String matchName = h.getASTMethodDeclarator().getImage();
                            int matchParamCount = h.getASTMethodDeclarator().getParameterCount();
                            if (methName.equals(matchName) && (methArgCount == matchParamCount)) {
                                ch.setDangerous(true);
                                //System.out.println("evaluateDangerOfConstructors1 setting dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params");
                                break;
                            }
                        }

                    }
                }
            }
        }
    }

    /**
     * Constructor map should contain a key for each private constructor, and
     * maps to a List which contains all called constructors of that key.
     * marks dangerous if call dangerous private constructor
     * we ignore all non-private constructors here.  That is, the map passed in
     * should not contain any non-private constructors.
     * we return boolean in order to limit the number of passes through this method
     * but it seems as if we can forgo that and just process it till its done.
     */
    private boolean evaluateDangerOfConstructors2(Map classConstructorMap) {
        boolean found = false;//triggers on danger state change
        //check each constructor in the class
        for (Iterator constIter = classConstructorMap.keySet().iterator(); constIter.hasNext();) {
            ConstructorHolder ch = (ConstructorHolder) constIter.next();
            ConstructorInvocation calledC = ch.getCalledConstructor();
            if (calledC == null || ch.isDangerous()) {
                continue;
            }
            //if its not dangerous then evaluate if it should be
            //if it calls dangerous constructor mark it as dangerous
            int cCount = calledC.getArgumentCount();
            for (Iterator innerConstIter = classConstructorMap.keySet().iterator(); innerConstIter.hasNext() && !ch.isDangerous();) { //forget skipping self because that introduces another check for each, but only 1 hit
                ConstructorHolder h2 = (ConstructorHolder) innerConstIter.next();
                if (h2.isDangerous()) {
                    int matchConstArgCount = h2.getASTConstructorDeclaration().getParameterCount();
                    if (matchConstArgCount == cCount) {
                        ch.setDangerous(true);
                        found = true;
                        //System.out.println("evaluateDangerOfConstructors2 setting dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params");
                    }
                }
            }
        }
        return found;
    }

    public Object visit(ASTCompilationUnit node, Object data) {
        clearEvalPackages();
        return super.visit(node, data);
    }

    public Object visit(ASTEnumDeclaration node, Object data) {
        // just skip Enums
        return data;
    }

    /**
     * This check must be evaluated independelty for each class.  Inner classses
     * get their own EvalPackage in order to perform independent evaluation.
     */
    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
        if (!node.isInterface()) {
            return visitClassDec(node, data);
        } else {
            putEvalPackage(nullEvalPackage);
            Object o = super.visit(node, data);//interface may have inner classes, possible? if not just skip whole interface
            removeCurrentEvalPackage();
            return o;
        }
    }


    /**
     * Non-private constructor's methods are added to a list for later safety
     * evaluation.  Non-private constructor's calls on private constructors
     * are added to a list for later safety evaluation.  Private constructors
     * are added to a list so their safety to be called can be later evaluated.
     * <p/>
     * Note: We are not checking private constructor's calls on non-private
     * constructors because all non-private constructors will be evaluated for
     * safety anyway.  This means we wont flag a private constructor as unsafe
     * just because it calls an unsafe public constructor.  We want to show only
     * 1 instance of an error, and this would be 2 instances of the same error.
     *
     * @todo eliminate the redundency
     */
    public Object visit(ASTConstructorDeclaration node, Object data) {
        if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {//only evaluate if we have an eval package for this class
            List calledMethodsOfConstructor = new ArrayList();
            ConstructorHolder ch = new ConstructorHolder(node);
            addCalledMethodsOfNode(node, calledMethodsOfConstructor, getCurrentEvalPackage().m_ClassName);
            if (!node.isPrivate()) {
                //these calledMethods are what we will evaluate for being called badly
                getCurrentEvalPackage().calledMethods.addAll(calledMethodsOfConstructor);
                //these called private constructors are what we will evaluate for being called badly
                //we add all constructors invoked by non-private constructors
                //but we are only interested in the private ones.  We just can't tell the difference here
                ASTExplicitConstructorInvocation eci = ch.getASTExplicitConstructorInvocation();
                if (eci != null && eci.isThis()) {
                    getCurrentEvalPackage().calledConstructors.add(ch.getCalledConstructor());
                }
            } else {
                //add all private constructors to list for later evaluation on if they are safe to call from another constructor
                //store this constructorHolder for later evaluation
                getCurrentEvalPackage().allPrivateConstructorsOfClass.put(ch, calledMethodsOfConstructor);
            }
        }
        return super.visit(node, data);
    }

    /**
     * Create a MethodHolder to hold the method.
     * Store the MethodHolder in the Map as the key
     * Store each method called by the current method as a List in the Map as the Object
     */
    public Object visit(ASTMethodDeclarator node, Object data) {
        if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {//only evaluate if we have an eval package for this class
            AccessNode parent = (AccessNode) node.jjtGetParent();
            MethodHolder h = new MethodHolder(node);
            if (!parent.isAbstract() && !parent.isPrivate() && !parent.isStatic() && !parent.isFinal()) { //Skip abstract methods, have a separate rule for that
                h.setDangerous();//this method is overridable
                ASTMethodDeclaration decl = (ASTMethodDeclaration) node.getFirstParentOfType(ASTMethodDeclaration.class);
                h.setCalledMethod(decl.getMethodName());
            }
            List l = new ArrayList();
            addCalledMethodsOfNode((SimpleNode) parent, l, getCurrentEvalPackage().m_ClassName);
            getCurrentEvalPackage().allMethodsOfClass.put(h, l);
        }
        return super.visit(node, data);
    }


    private static void addCalledMethodsOfNode(AccessNode node, List calledMethods, String className) {
        List expressions = new ArrayList();
        node.findChildrenOfType(ASTPrimaryExpression.class, expressions, false);
        addCalledMethodsOfNodeImpl(expressions, calledMethods, className);
    }

    /**
     * Adds all methods called on this instance from within this Node.
     */
    private static void addCalledMethodsOfNode(SimpleNode node, List calledMethods, String className) {
        List expressions = new ArrayList();
        node.findChildrenOfType(ASTPrimaryExpression.class, expressions);
        addCalledMethodsOfNodeImpl(expressions, calledMethods, className);
    }

    private static void addCalledMethodsOfNodeImpl(List expressions, List calledMethods, String className) {
        for (Iterator it = expressions.iterator(); it.hasNext();) {
            ASTPrimaryExpression ape = (ASTPrimaryExpression) it.next();
            MethodInvocation meth = findMethod(ape, className);
            if (meth != null) {
                //System.out.println("Adding call " + methName);
                calledMethods.add(meth);
            }
        }
    }

    /**
     * @return A method call on the class passed in, or null if no method call
     *         is found.
     * @todo Need a better way to match the class and package name to the actual
     * method being called.
     */
    private static MethodInvocation findMethod(ASTPrimaryExpression node, String className) {
        if (node.jjtGetNumChildren() > 0
                && node.jjtGetChild(0).jjtGetNumChildren() > 0
                && node.jjtGetChild(0).jjtGetChild(0) instanceof ASTLiteral) {
            return null;
        }
        MethodInvocation meth = MethodInvocation.getMethod(node);
        boolean found = false;
        //		if(meth != null){
        //			meth.show();
        //		}
        if (meth != null) {
            //if it's a call on a variable, or on its superclass ignore it.
            if ((meth.getReferenceNames().size() == 0) && !meth.isSuper()) {
                //if this list does not contain our class name, then its not referencing our class
                //this is a cheezy test... but it errs on the side of less false hits.
                List packClass = meth.getQualifierNames();
                if (packClass.size() > 0) {
                    for (Iterator it = packClass.iterator(); it.hasNext();) {
                        String name = (String) it.next();
                        if (name.equals(className)) {
                            found = true;
                            break;
                        }
                    }
                } else {
                    found = true;
                }
            }
        }

        return found ? meth : null;
    }

    /**
     * ASTPrimaryPrefix has name in child node of ASTName
     */
    private static String getNameFromPrefix(ASTPrimaryPrefix node) {
        String name = null;
        //should only be 1 child, if more I need more knowledge
        if (node.jjtGetNumChildren() == 1) { //safety check
            Node nnode = node.jjtGetChild(0);
            if (nnode instanceof ASTName) { //just as easy as null check and it should be an ASTName anyway
                name = ((ASTName) nnode).getImage();
            }
        }
        return name;
    }

}
