/*
 * 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.netbeans.modules.refactoring.java.plugins;

import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.lang.model.element.*;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import org.netbeans.api.java.source.*;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.api.RenameRefactoring;
import org.netbeans.modules.refactoring.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.api.ChangeParametersRefactoring;
import org.netbeans.modules.refactoring.java.api.ChangeParametersRefactoring.ParameterInfo;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.spi.JavaRefactoringPlugin;
import org.netbeans.modules.refactoring.java.ui.ChangeParametersPanel.Javadoc;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups;

/**
 * Refactoring used for changing method signature. It changes method declaration
 * and also all its references (callers).
 *
 * @author  Pavel Flaska
 * @author  Tomas Hurka
 * @author  Jan Becicka
 * @author  Ralph Ruijs
 */
public class ChangeParametersPlugin extends JavaRefactoringPlugin {
    
    private ChangeParametersRefactoring refactoring;
    private TreePathHandle treePathHandle;
    private boolean inited;
    
    /**
     * Creates a new instance of change parameters refactoring.
     *
     * @param method  refactored object, i.e. method or constructor
     */
    public ChangeParametersPlugin(ChangeParametersRefactoring refactoring) {
        this.refactoring = refactoring;
        this.treePathHandle = refactoring.getRefactoringSource().lookup(TreePathHandle.class);
    }
    
    @Override
    public Problem checkParameters() {
        Problem p = null;
        // TODO: Rename checks
        return p;
    }

    @Override
    public Problem fastCheckParameters(final CompilationController javac) throws IOException {
        javac.toPhase(JavaSource.Phase.RESOLVED);
        ParameterInfo paramTable[] = refactoring.getParameterInfo();
        
        final ExecutableElement method = (ExecutableElement) treePathHandle.resolveElement(javac);
        Problem p=null;
        boolean isConstructor = method.getKind() == ElementKind.CONSTRUCTOR;
        final Checks check = new Checks(javac);
        
        p = check.methodName(p, refactoring.getMethodName());
        
        TypeElement enclosingTypeElement = javac.getElementUtilities().enclosingTypeElement(method);
        List<? extends Element> allMembers = javac.getElements().getAllMembers(enclosingTypeElement);

        if(!isConstructor) {
            p = check.returnType(p, refactoring.getReturnType(), method, enclosingTypeElement);
            p = check.duplicateSignature(p, paramTable, method, enclosingTypeElement, allMembers);
            p = check.accessModifiers(p, refactoring.getModifiers() != null ? refactoring.getModifiers() : method.getModifiers(), method, enclosingTypeElement, paramTable, cancelRequested);
        } else {
            p = check.duplicateConstructor(p, paramTable, method, enclosingTypeElement, allMembers);
        }
        for (int i = 0; i< paramTable.length; i++) {
            ParameterInfo parameterInfo = paramTable[i];

            p = check.checkParameterName(p, parameterInfo.getName());
            if (parameterInfo.getOriginalIndex() == -1) {
                p = check.defaultValue(p, parameterInfo.getDefaultValue());
            }
            p = check.duplicateParamName(p, paramTable, i);
            p = check.duplicateLocalName(p, paramTable, i, method);
            p = check.parameterType(p, paramTable, i, method, enclosingTypeElement);
        }
        return p;
    }
    
    private static String newParMessage(String par) {
        return new MessageFormat(
                getString("ERR_newpar")).format(new Object[] { getString(par) } // NOI18N
            );
    }
    
    private static String getString(String key) {
        return NbBundle.getMessage(ChangeParametersPlugin.class, key);
    }

    private Set<ElementHandle<ExecutableElement>> allMethods;
    
    private Set<FileObject> getRelevantFiles() {
        ClasspathInfo cpInfo = getClasspathInfo(refactoring);
        final Set<FileObject> set = new LinkedHashSet<FileObject>();
        TreePathHandle tph = refactoring.getRefactoringSource().lookup(TreePathHandle.class);
        set.add(tph.getFileObject());
        JavaSource source = JavaSource.create(cpInfo, tph.getFileObject());
        
        try {
            source.runUserActionTask(new CancellableTask<CompilationController>() {
                
                @Override
                public void cancel() {
                    throw new UnsupportedOperationException("Not supported yet."); // NOI18N
                }
                
                @Override
                public void run(CompilationController info) throws Exception {
                    final ClassIndex idx = info.getClasspathInfo().getClassIndex();
                    info.toPhase(JavaSource.Phase.RESOLVED);
                    final ElementUtilities elmUtils = info.getElementUtilities();

                    //add all references of overriding methods
                    ExecutableElement el = (ExecutableElement)treePathHandle.resolveElement(info);
                    ElementHandle<ExecutableElement> methodHandle = ElementHandle.create(el);
                    ElementHandle<TypeElement>  enclosingType = ElementHandle.create(elmUtils.enclosingTypeElement(el));
                    allMethods = new HashSet<ElementHandle<ExecutableElement>>();
                    allMethods.add(methodHandle);
                    for (ExecutableElement e:JavaRefactoringUtils.getOverridingMethods(el, info,cancelRequested)) {
                        ElementHandle<ExecutableElement> handle = ElementHandle.create(e);
                        set.add(SourceUtils.getFile(handle, info.getClasspathInfo()));
                        ElementHandle<TypeElement> encl = ElementHandle.create(elmUtils.enclosingTypeElement(e));
                        set.addAll(idx.getResources(encl, EnumSet.of(ClassIndex.SearchKind.METHOD_REFERENCES),EnumSet.of(ClassIndex.SearchScope.SOURCE)));
                        allMethods.add(ElementHandle.create(e));
                    }
                    //add all references of overriden methods
                    for (ExecutableElement e:JavaRefactoringUtils.getOverriddenMethods(el, info)) {
                        ElementHandle<ExecutableElement> handle = ElementHandle.create(e);
                        set.add(SourceUtils.getFile(handle, info.getClasspathInfo()));
                        ElementHandle<TypeElement> encl = ElementHandle.create(elmUtils.enclosingTypeElement(e));
                        set.addAll(idx.getResources(encl, EnumSet.of(ClassIndex.SearchKind.METHOD_REFERENCES),EnumSet.of(ClassIndex.SearchScope.SOURCE)));
                        allMethods.add(ElementHandle.create(e));
                    }
                    set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.METHOD_REFERENCES),EnumSet.of(ClassIndex.SearchScope.SOURCE)));
                    set.add(SourceUtils.getFile(methodHandle, info.getClasspathInfo()));
                }
            }, true);
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);
        }
        return set;
    }
    
    
    @Override
    public Problem prepare(RefactoringElementsBag elements) {
        Set<FileObject> a = getRelevantFiles();
        fireProgressListenerStart(AbstractRefactoring.PREPARE, (a.size() * 2) + 1);
        Problem problem = null;
        if (!a.isEmpty()) {
            fireProgressListenerStep();

            ChangeParamsTransformer changeParamsTransformer = new ChangeParamsTransformer(refactoring.getParameterInfo(),
                    refactoring.getModifiers(),
                    refactoring.getReturnType(),
                    refactoring.getMethodName(),
                    refactoring.isOverloadMethod(),
                    refactoring.getContext().lookup(Javadoc.class),
                    allMethods,
                    treePathHandle);
            
//            ChangeParamsJavaDocTransformer changeJavaDocParamsTransformer = new ChangeParamsJavaDocTransformer(refactoring.getParameterInfo(),
//                    refactoring.getReturnType(),
//                    refactoring.isOverloadMethod(),
//                    refactoring.getContext().lookup(Javadoc.class),
//                    allMethods,
//                    treePathHandle);
//
//            TransformTask transformJavadoc = new TransformTask(changeJavaDocParamsTransformer, treePathHandle);
//            problem = JavaPluginUtils.chainProblems(problem, createAndAddElements(a, transformJavadoc, elements, refactoring));
            TransformTask transform = new TransformTask(changeParamsTransformer, treePathHandle);
            problem = JavaPluginUtils.chainProblems(problem, createAndAddElements(a, transform, elements, refactoring, getClasspathInfo(refactoring)));
            problem = JavaPluginUtils.chainProblems(problem, changeParamsTransformer.getProblem());
        }
        fireProgressListenerStop();
        return problem;
    }
    
    @Override
    protected JavaSource getJavaSource(JavaRefactoringPlugin.Phase p) {
        switch(p) {
            case CHECKPARAMETERS:
            case FASTCHECKPARAMETERS:    
            case PRECHECK:
            case PREPARE:
                ClasspathInfo cpInfo = getClasspathInfo(refactoring);
                return JavaSource.create(cpInfo, treePathHandle.getFileObject());
        }
        return null;
    }
    /**
     * Returns list of problems. For the change method signature, there are two
     * possible warnings - if the method is overriden or if it overrides
     * another method.
     *
     * @return  overrides or overriden problem or both
     */
    @Override
    public Problem preCheck(CompilationController info) throws IOException {
        fireProgressListenerStart(refactoring.PRE_CHECK, 4);
        Problem preCheckProblem = null;
        info.toPhase(JavaSource.Phase.RESOLVED);
        preCheckProblem = isElementAvail(treePathHandle, info);
        if (preCheckProblem != null) {
            return preCheckProblem;
        }
        Element el = treePathHandle.resolveElement(info);
        if (!RefactoringUtils.isExecutableElement(el)) {
            preCheckProblem = createProblem(preCheckProblem, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_ChangeParamsWrongType")); // NOI18N
            return preCheckProblem;
        }

        preCheckProblem = JavaPluginUtils.isSourceElement(el, info);
        if (preCheckProblem != null) {
            return preCheckProblem;
        }

        if (info.getElementUtilities().enclosingTypeElement(el).getKind() == ElementKind.ANNOTATION_TYPE) {
            preCheckProblem =new Problem(true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_MethodsInAnnotationsNotSupported")); // NOI18N
            return preCheckProblem;
        }
        
        for (ExecutableElement e : JavaRefactoringUtils.getOverriddenMethods((ExecutableElement) el, info)) {
            ElementHandle<ExecutableElement> handle = ElementHandle.create(e);
            if (RefactoringUtils.isFromLibrary(handle, info.getClasspathInfo())) { //NOI18N
                preCheckProblem = createProblem(preCheckProblem, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_CannnotRefactorLibrary", el)); // NOI18N
            }
        }
                    
        fireProgressListenerStop();
        return preCheckProblem;
    }
    
//    private void initDelegates() {
//        if (inited) {
//            return;
//        }
//        final LinkedList<RenameRefactoring> renameRefactoringsList = new LinkedList<RenameRefactoring>();
//
//        try {
//            getJavaSource(Phase.PREPARE).runUserActionTask(new Task<CompilationController>() {
//
//                @Override
//                public void run(CompilationController javac) throws Exception {
//                    javac.toPhase(JavaSource.Phase.RESOLVED);
//                    ExecutableElement method = (ExecutableElement) treePathHandle.resolveElement(javac);
//                    List<? extends VariableElement> parameters = method.getParameters();
//
//                    ParameterInfo paramTable[] = refactoring.getParameterInfo();
//                    for (int i = 0; i < paramTable.length; i++) {
//                        ParameterInfo param = paramTable[i];
//                        int origIndex = param.getOriginalIndex();
//                        if (origIndex != -1) {
//                            VariableElement variable = parameters.get(origIndex);
//                            if(!variable.getSimpleName().contentEquals(param.getName())) {
//                                TreePath path = javac.getTrees().getPath(variable);
//                                RenameRefactoring renameRefactoring = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(path, javac)));
//                                renameRefactoring.setNewName(param.getName());
//                                renameRefactoring.setSearchInComments(true);
//                                renameRefactoringsList.add(renameRefactoring);
//                            }
//                        }
//                    }
//                    if(refactoring.getMethodName() != null && !method.getSimpleName().toString().equals(refactoring.getMethodName())) {
//                        RenameRefactoring renameRefactoring = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(method, javac)));
//                        renameRefactoring.setNewName(refactoring.getMethodName());
//                        renameRefactoring.setSearchInComments(true);
//                        renameRefactoringsList.add(renameRefactoring);
//                    }
//                }
//            }, true);
//        } catch (IOException ex) {
//            Exceptions.printStackTrace(ex);
//        }
//        inited = true;
//    }
    
    static class Checks {
        protected CompilationController javac;

        public Checks(CompilationController javac) {
            this.javac = javac;
        }
        
        Problem methodName(Problem p, String methodName) {
            if(methodName != null && !Utilities.isJavaIdentifier(methodName)) {
                p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_InvalidIdentifier", methodName)); // NOI18N
            }
            return p;
        }

        Problem defaultValue(Problem p, String defaultValue) {
            if ((defaultValue == null || defaultValue.length() < 1)) {
                p = createProblem(p, true, newParMessage("ERR_pardefv")); // NOI18N
            }
            return p;
        }
        
        Problem checkParameterName(Problem p, String s) {
            if ((s == null || s.length() < 1)) {
                p = createProblem(p, true, newParMessage("ERR_parname"));
            } // NOI18N
            else {
                if (!Utilities.isJavaIdentifier(s)) {
                    p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_InvalidIdentifier",s)); // NOI18N
                }
            }

            return p;
        }
        
        Problem duplicateParamName(Problem p, ParameterInfo[] paramTable, int index) {
            String name = paramTable[index].getName();
            for (int j = 0; j < paramTable.length; j++) {
                ParameterInfo pInfo = paramTable[j];
                if(pInfo.getName().equals(name) && index != j) {
                    p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_ParamAlreadyUsed", name)); // NOI18N
                }
            }
            return p;
        }


        Problem returnType(Problem p, String returnType, ExecutableElement method, TypeElement enclosingTypeElement) {
            TypeMirror parseType;
            if (returnType != null && returnType.length() < 1) {
                p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_NoReturn", returnType)); // NOI18N
            } else if (returnType != null) {
                TypeElement typeElement = javac.getElements().getTypeElement(returnType);
                parseType = typeElement == null ? null : typeElement.asType();
                if(parseType == null) {
                    boolean isGenericType = false;
                    List<? extends TypeParameterElement> typeParameters = method.getTypeParameters();
                    for (TypeParameterElement typeParameterElement : typeParameters) {
                        TypeParameterTree tpTree = (TypeParameterTree) javac.getTrees().getTree(typeParameterElement);
                        if(returnType.equals(tpTree.getName().toString())) {
                            isGenericType = true;
                        }
                    }
                    if(!isGenericType) {
                        parseType = javac.getTreeUtilities().parseType(returnType, enclosingTypeElement);
                        if(parseType == null || parseType.getKind() == TypeKind.ERROR) {
                            p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "WRN_canNotResolveReturn", returnType)); // NOI18N
                        }
                    }
                }
            }
            return p;
        }

        Problem duplicateSignature(Problem p, ParameterInfo[] paramTable, final ExecutableElement method, TypeElement enclosingTypeElement, List<? extends Element> allMembers) {
            List<ExecutableElement> methods = ElementFilter.methodsIn(allMembers);
            for (ExecutableElement exMethod : methods) {
                if(!exMethod.equals(method)) {
                    if(exMethod.getSimpleName().equals(method.getSimpleName())
                            && exMethod.getParameters().size() == paramTable.length) {
                        boolean sameParameters = true;
                        boolean wideningConversion = true;
                        for (int j = 0; j < exMethod.getParameters().size(); j++) {
                            TypeMirror exType = ((VariableElement)exMethod.getParameters().get(j)).asType();
                            String type = paramTable[j].getType();
                            TypeMirror paramType = javac.getTreeUtilities().parseType(type, enclosingTypeElement);
                            if(!javac.getTypes().isSameType(exType, paramType)) {
                                sameParameters = false;
                                if(exType.getKind().isPrimitive() && paramType.getKind().isPrimitive()) {
                                    /*
                                     * byte to short, int, long, float, or double
                                     * short to int, long, float, or double
                                     * char to int, long, float, or double
                                     * int to long, float, or double
                                     * long to float or double
                                     * float to double
                                     */
                                    switch (exType.getKind()) {
                                        case DOUBLE:
                                            if(paramType.getKind().equals(TypeKind.FLOAT)) {
                                                break;
                                            }
                                        case FLOAT:
                                            if(paramType.getKind().equals(TypeKind.LONG)) {
                                                break;
                                            }
                                        case LONG:
                                            if(paramType.getKind().equals(TypeKind.INT)) {
                                                break;
                                            }
                                        case INT:
                                            if(paramType.getKind().equals(TypeKind.SHORT)) {
                                                break;
                                            }
                                        case SHORT:
                                            if(paramType.getKind().equals(TypeKind.BYTE)) {
                                                break;
                                            }
                                        case BYTE:
                                            wideningConversion = false;
                                            break;
                                    }
                                } else {
                                    wideningConversion = false;
                                }
                            }
                        }
                        if(sameParameters) {
                            p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_existingMethod", exMethod.toString(), enclosingTypeElement.getQualifiedName())); // NOI18N
                        } else if(wideningConversion) {
                            p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "WRN_wideningConversion", exMethod.toString(), enclosingTypeElement.getQualifiedName())); // NOI18N
                        }
                    }
                }
            }
            return p;
        }

        Problem parameterType(Problem p, ParameterInfo[] paramTable, int index, ExecutableElement method, TypeElement enclosingTypeElement) {
            String type = paramTable[index].getType();
            String name = paramTable[index].getName();
            type = type.split("<", 2)[0];
            String[] split = type.split(" "); //NOI18N
            type = split[split.length-1];
            TypeMirror parseType = null;
            if (type == null || type.length() < 1) {
                p = createProblem(p, true, newParMessage("ERR_partype")); // NOI18N
            } else {
                TypeElement typeElement = javac.getElements().getTypeElement(type);
                parseType = typeElement == null ? null : typeElement.asType();
                if(parseType == null) {
                    boolean isGenericType = false;
                    List<? extends TypeParameterElement> typeParameters = method.getTypeParameters();
                    for (TypeParameterElement typeParameterElement : typeParameters) {
                        TypeParameterTree tpTree = (TypeParameterTree) javac.getTrees().getTree(typeParameterElement);
                        if(type.equals(tpTree.getName().toString())) {
                            isGenericType = true;
                        }
                    }
                    if(!isGenericType) {
                        parseType = javac.getTreeUtilities().parseType(type, enclosingTypeElement);
                        if(parseType == null || parseType.getKind() == TypeKind.ERROR) {
                            p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "WRN_canNotResolve", type, name)); // NOI18N
                        }
                    }
                }
            }
            // check if the changed type is assigneble
            int originalIndex = paramTable[index].getOriginalIndex();
            if(parseType != null && originalIndex > -1) {
                final VariableElement parameterElement = method.getParameters().get(originalIndex);
                if(!javac.getTypes().isAssignable(parseType, parameterElement.asType())) {
                    if(type != null && type.endsWith("...") && parameterElement.asType().getKind() == TypeKind.ARRAY) { //NOI18N
                        ArrayType arrayType = (ArrayType) parameterElement.asType();
                        if(!javac.getTypes().isAssignable(parseType, arrayType.getComponentType())) {
                            p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "WRN_isNotAssignable", parameterElement.asType().toString(), type)); // NOI18N
                        }
                    } else {
                        p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "WRN_isNotAssignable", parameterElement.asType().toString(), type)); // NOI18N
                    }
                }
            }
            // check ...
            if (type != null && type.endsWith("...") && index != paramTable.length - 1) {//NOI18N
                p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_VarargsFinalPosition", new Object[]{})); // NOI18N
            }
            return p;
        }

        Problem duplicateLocalName(Problem p, ParameterInfo[] paramTable, int index, ExecutableElement method) {
            String name = paramTable[index].getName();
            int originalIndex = paramTable[index].getOriginalIndex();
            final VariableElement parameterElement = originalIndex == -1 ? null : method.getParameters().get(originalIndex);
            ErrorAwareTreeScanner<Boolean, String> scanner = new ErrorAwareTreeScanner<Boolean, String>() {

                @Override
                public Boolean visitVariable(VariableTree vt, String p) {
                    super.visitVariable(vt, p);
                    TreePath path = javac.getTrees().getPath(javac.getCompilationUnit(), vt);
                    Element element = javac.getTrees().getElement(path);

                    return vt.getName().contentEquals(p) && (element == null || !element.equals(parameterElement));
                }

                @Override
                public Boolean reduce(Boolean left, Boolean right) {
                    return (left == null ? false : left) || (right == null ? false : right);
                }
            };

            if (scanner.scan(javac.getTrees().getTree(method), name)) {
                if (!isParameterBeingRemoved(method, name, paramTable)) {
                    p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_NameAlreadyUsed", name)); // NOI18N
                }
            }
            return p;
        }

        Problem duplicateConstructor(Problem p, ParameterInfo[] paramTable, final ExecutableElement method, TypeElement enclosingTypeElement, List<? extends Element> allMembers) {
            List<ExecutableElement> constructors = ElementFilter.constructorsIn(allMembers);
            for (ExecutableElement constructor : constructors) {
                if(!constructor.equals(method)) {
                    if(constructor.getParameters().size() == paramTable.length) {
                        boolean sameParameters = true;
                        for (int j = 0; j < constructor.getParameters().size(); j++) {
                            TypeMirror exType = ((VariableElement)constructor.getParameters().get(j)).asType();
                            String type = paramTable[j].getType();
                            TypeMirror paramType = javac.getTreeUtilities().parseType(type, enclosingTypeElement);
                            if(!javac.getTypes().isSameType(exType, paramType)) {
                                sameParameters = false;
                            }
                        }
                        if(sameParameters) {
                            p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_existingConstructor", constructor.toString(), enclosingTypeElement.getQualifiedName())); // NOI18N
                        }
                    }
                }
            }
            return p;
        }
        
        
        private Problem accessModifiers(Problem p, Set<Modifier> modifiers, ExecutableElement method, TypeElement enclosingTypeElement, ParameterInfo[] paramTable, AtomicBoolean cancel) {
            List<ExecutableElement> allMethods = findDuplicateSubMethods(enclosingTypeElement, method, paramTable, cancel);
            if(!allMethods.isEmpty()) {
                Collection<ExecutableElement> overridingMethods = JavaRefactoringUtils.getOverridingMethods(method, javac,cancel);
                boolean willBeOverriden = false;
                for (ExecutableElement executableElement : allMethods) {
                    if(!overridingMethods.contains(executableElement)) {
                        willBeOverriden = true;
                        break;
                    }
                }
                if(willBeOverriden) {
                    p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "WRN_MethodIsOverridden", enclosingTypeElement.toString())); // NOI18N
                }
            }
            for (ExecutableElement exMethod : allMethods) {
                if(!javac.getTypes().isSameType(exMethod.getReturnType(),method.getReturnType())) {
                    p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_existingReturnType", exMethod.getSimpleName(), exMethod.getEnclosingElement().getSimpleName(), method.getReturnType().toString(), exMethod.getReturnType().toString())); // NOI18N
                }
            }
            for (ExecutableElement exMethod : allMethods) {
                if(!modifiers.contains(Modifier.PRIVATE)) {
                    if(RefactoringUtils.isWeakerAccess(exMethod.getModifiers(), modifiers)) {
                        p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_WeakerAccess", exMethod.getSimpleName(), exMethod.getEnclosingElement().getSimpleName())); // NOI18N
                    }
                }
            }
            return p;
        }

        private List<ExecutableElement> findDuplicateSubMethods(TypeElement enclosingTypeElement, ExecutableElement method, ParameterInfo[] paramTable, AtomicBoolean cancel) {
            List<ExecutableElement> returnmethods = new LinkedList<ExecutableElement>();
            Set<ElementHandle<TypeElement>> subTypes = RefactoringUtils.getImplementorsAsHandles(javac.getClasspathInfo().getClassIndex(), javac.getClasspathInfo(), enclosingTypeElement, cancel);
            for (ElementHandle<TypeElement> elementHandle : subTypes) {
                TypeElement subtype = elementHandle.resolve(javac);
                if(subtype != null) {
                    List<ExecutableElement> methods = ElementFilter.methodsIn(javac.getElements().getAllMembers(subtype));
                    for (ExecutableElement exMethod : methods) {
                        if (!exMethod.equals(method)) {
                            if (exMethod.getSimpleName().equals(method.getSimpleName())
                                    && exMethod.getParameters().size() == paramTable.length) {
                                boolean sameParameters = true;
                                for (int j = 0; j < exMethod.getParameters().size(); j++) {
                                    TypeMirror exType = ((VariableElement) exMethod.getParameters().get(j)).asType();
                                    String type = paramTable[j].getType();
                                    if (type == null || type.length() == 0) {
                                        sameParameters = false;
                                    } else {
                                        TypeMirror paramType = javac.getTreeUtilities().parseType(type, enclosingTypeElement);
                                        if (!javac.getTypes().isSameType(exType, paramType)) {
                                            sameParameters = false;
                                        }
                                    }
                                }
                                if (sameParameters) {
                                    returnmethods.add(exMethod);
                                }
                            }
                        }
                    }
                }
            }
            return returnmethods;
        }

        boolean isParameterBeingRemoved(ExecutableElement method, String s, ParameterInfo[] paramTable) {
            boolean beingRemoved = false;
            for (int j = 0; j < method.getParameters().size(); j++) {
                VariableElement variable = method.getParameters().get(j);
                if (variable.getSimpleName().contentEquals(s)) {

                    boolean isInNewList = false;
                    for (ParameterInfo parameterInfo : paramTable) {
                        if (parameterInfo.getOriginalIndex() == j) {
                            isInNewList = true;
                        }
                    }
                    beingRemoved = !isInNewList;
                    break;
                }
            }
            return beingRemoved;
        }
    }
}
