/*
 * 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;

import com.sun.source.tree.*;
import com.sun.source.util.TreePath;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.*;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;
import javax.swing.text.Document;
import org.netbeans.api.annotations.common.NullUnknown;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.ClassPath.Entry;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.java.queries.SourceForBinaryQuery.Result;
import org.netbeans.api.java.queries.UnitTestForSourceQuery;
import org.netbeans.api.java.source.*;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.modules.refactoring.java.plugins.LocalVarScanner;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.text.CloneableEditorSupport;
import org.openide.text.NbDocument;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.windows.TopComponent;

/**
 *
 * @author Jan Becicka
 */
public class RefactoringUtils {

    private static final String JAVA_MIME_TYPE = "text/x-java"; // NOI18N
    private static final Logger LOG = Logger.getLogger(RefactoringUtils.class.getName());

    /**
     * Get all overriding methods for given ExecutableElement
     *
     * @param e
     * @param info
     * @return
     * @deprecated 
     */
    @Deprecated
    public static Collection<ExecutableElement> getOverridenMethods(ExecutableElement e, CompilationInfo info) {
        return getOverridenMethods(e, info.getElementUtilities().enclosingTypeElement(e), info);
    }

    private static Collection<ExecutableElement> getOverridenMethods(ExecutableElement e, TypeElement parent, CompilationInfo info) {
        ArrayList<ExecutableElement> result = new ArrayList<ExecutableElement>();

        TypeMirror sup = parent.getSuperclass();
        if (sup.getKind() == TypeKind.DECLARED) {
            TypeElement next = (TypeElement) ((DeclaredType) sup).asElement();
            ExecutableElement overriden = getMethod(e, next, info);
            result.addAll(getOverridenMethods(e, next, info));
            if (overriden != null) {
                result.add(overriden);
            }
        }
        for (TypeMirror tm : parent.getInterfaces()) {
            TypeElement next = (TypeElement) ((DeclaredType) tm).asElement();
            ExecutableElement overriden2 = getMethod(e, next, info);
            result.addAll(getOverridenMethods(e, next, info));
            if (overriden2 != null) {
                result.add(overriden2);
            }
        }
        return result;
    }

    private static ExecutableElement getMethod(ExecutableElement method, TypeElement type, CompilationInfo info) {
        for (ExecutableElement met : ElementFilter.methodsIn(type.getEnclosedElements())) {
            if (info.getElements().overrides(method, met, type)) {
                return met;
            }
        }
        return null;
    }

    public static Set<ElementHandle<TypeElement>> getImplementorsAsHandles(ClassIndex idx, ClasspathInfo cpInfo, TypeElement el, AtomicBoolean cancel) {
        LinkedList<ElementHandle<TypeElement>> elements = new LinkedList<ElementHandle<TypeElement>>(
                implementorsQuery(idx, ElementHandle.create(el)));
        Set<ElementHandle<TypeElement>> result = new HashSet<ElementHandle<TypeElement>>();
        while (!elements.isEmpty()) {
            if (cancel.get()) {
                return Collections.emptySet();
            }
            ElementHandle<TypeElement> next = elements.removeFirst();
            if (!result.add(next)) {
                // it is a duplicate; do not query again
                continue;
            }
            Set<ElementHandle<TypeElement>> foundElements = implementorsQuery(idx, next);
            elements.addAll(foundElements);
        }
        return result;
    }

    private static Set<ElementHandle<TypeElement>> implementorsQuery(ClassIndex idx, ElementHandle<TypeElement> next) {
        return idx.getElements(next,
                EnumSet.of(ClassIndex.SearchKind.IMPLEMENTORS),
                EnumSet.of(ClassIndex.SearchScope.SOURCE, ClassIndex.SearchScope.DEPENDENCIES));
    }

    /**
     * 
     * @param e
     * @param info
     * @param cancel 
     * @return
     * @deprecated
     */
    @Deprecated
    public static Collection<ExecutableElement> getOverridingMethods(ExecutableElement e, CompilationInfo info, AtomicBoolean cancel) {
        Collection<ExecutableElement> result = new ArrayList();
        TypeElement parentType = (TypeElement) e.getEnclosingElement();
        Set<ElementHandle<TypeElement>> subTypes = getImplementorsAsHandles(info.getClasspathInfo().getClassIndex(), info.getClasspathInfo(), parentType, cancel);
        for (ElementHandle<TypeElement> subTypeHandle : subTypes) {
            TypeElement type = subTypeHandle.resolve(info);
            if (type == null) {
                // #214462: removed logging, logs show coupling errors
                continue;
                // #120577: log info to find out what is going wrong
//                FileObject file = SourceUtils.getFile(subTypeHandle, info.getClasspathInfo());
//                if (file == null) {
//                    //Deleted file
//                    continue;
//                } else {
//                    throw new NullPointerException("#120577: Cannot resolve " + subTypeHandle + "; file: " + file + " Classpath: " + info.getClasspathInfo());
//                }
            }
            List<ExecutableElement> methods = new LinkedList<>(ElementFilter.methodsIn(type.getEnclosedElements()));
            // #253063 - Anonymous classes of enum constants are not returned by index, need to get them manually
            if(type.getKind() == ElementKind.ENUM) {
                for (VariableElement variableElement : ElementFilter.fieldsIn(type.getEnclosedElements())) {
                    TreePath varPath = info.getTrees().getPath(variableElement);
                    if(varPath != null && varPath.getLeaf().getKind() == Tree.Kind.VARIABLE) {
                        ExpressionTree initializer = ((VariableTree)varPath.getLeaf()).getInitializer();
                        if(initializer != null && initializer.getKind() == Tree.Kind.NEW_CLASS) {
                            NewClassTree ncTree = (NewClassTree) initializer;
                            ClassTree classBody = ncTree.getClassBody();
                            if(classBody != null) {
                                Element anonEl = info.getTrees().getElement(new TreePath(varPath, classBody));
                                if(anonEl != null) {
                                    methods.addAll(ElementFilter.methodsIn(anonEl.getEnclosedElements()));
                                }
                            }
                        }
                    }
                }
            }
            for (ExecutableElement method : methods) {
                if (info.getElements().overrides(method, e, type)) {
                    result.add(method);
                }
            }
        }
        return result;
    }
    
    public static CodeStyle getCodeStyle(CompilationInfo info) {
        if (info != null) {
            try {
                Document doc = info.getDocument();
                if (doc != null) {
                    CodeStyle cs = (CodeStyle)doc.getProperty(CodeStyle.class);
                    return cs != null ? cs : CodeStyle.getDefault(doc);
                }
            } catch (IOException ioe) {
                // ignore
            }

            FileObject file = info.getFileObject();
            if (file != null) {
                return CodeStyle.getDefault(file);
            }
        }

        return CodeStyle.getDefault((Document)null);
    }

    /**
     *
     * @param f
     * @return true if f is java
     */
    public static boolean isJavaFile(FileObject f) {
        return JAVA_MIME_TYPE.equals(FileUtil.getMIMEType(f, JAVA_MIME_TYPE));
    }

    /**
     * @param element
     * @param info
     * @return true if given element comes from library
     */
    public static boolean isFromLibrary(ElementHandle<? extends Element> element, ClasspathInfo info) {
        FileObject file = SourceUtils.getFile(element, info);
        if (file == null) {
            //no source for given element. Element is from library
            return true;
        }
        return FileUtil.getArchiveFile(file) != null;
    }

    /**
     * is given name valid package name
     *
     * @param name
     * @return
     */
    public static boolean isValidPackageName(String name) {
        if (name.endsWith(".")) //NOI18N
        {
            return false;
        }
        if (name.startsWith(".")) //NOI18N
        {
            return false;
        }
        if (name.contains("..")) //NOI18N
        {
            return false;
        }
        StringTokenizer tokenizer = new StringTokenizer(name, "."); // NOI18N
        while (tokenizer.hasMoreTokens()) {
            if (!Utilities.isJavaIdentifier(tokenizer.nextToken())) {
                return false;
            }
        }
        return true;
    }

    /**
     *
     * @param f
     * @return true if given file is in open project
     */
    public static boolean isFileInOpenProject(FileObject file) {
        assert file != null;
        // Future<Project[]> o.n.api.project.ui.OpenProjects.openProjects()
        Future<Project[]> openProjects = OpenProjects.getDefault().openProjects();
        if(!openProjects.isDone()) {
            return false;
        }
        Project p = FileOwnerQuery.getOwner(file);
        if (p == null) {
            return false;
        }
        return isOpenProject(p);
    }

    /**
     * Is given file on any source classpath?
     *
     * @param fo
     * @return
     * @deprecated 
     */
    @Deprecated
    public static boolean isOnSourceClasspath(FileObject fo) {
        Project pr = FileOwnerQuery.getOwner(fo);
        if (pr == null) {
            return false;
        }

        //workaround for 143542
        for (String type : new String[]{JavaProjectConstants.SOURCES_TYPE_JAVA, JavaProjectConstants.SOURCES_TYPE_RESOURCES}) {
            for (SourceGroup sg : ProjectUtils.getSources(pr).getSourceGroups(type)) {
                if (fo == sg.getRootFolder() || (FileUtil.isParentOf(sg.getRootFolder(), fo) && sg.contains(fo))) {
                    return ClassPath.getClassPath(fo, ClassPath.SOURCE) != null;
                }
            }
        }
        return false;
        //end of workaround
        //return ClassPath.getClassPath(fo, ClassPath.SOURCE)!=null;
    }

    /**
     * Is given file a root of source classpath?
     *
     * @param fo
     * @return
     */
    public static boolean isClasspathRoot(FileObject fo) {
        ClassPath cp = ClassPath.getClassPath(fo, ClassPath.SOURCE);
        return cp != null ? fo.equals(cp.findOwnerRoot(fo)) : false;
    }

    /**
     * Is the given file "java" && in open projects && on source classpath?
     *
     * @param file
     * @return
     * @deprecated 
     */
    @Deprecated
    public static boolean isRefactorable(FileObject file) {
        return file != null && isJavaFile(file) && isFileInOpenProject(file) && isOnSourceClasspath(file);
    }

    /**
     * returns package name for given folder. Folder must be on source classpath
     *
     * @param folder
     * @return
     */
    public static String getPackageName(FileObject folder) {
        assert folder.isFolder() : "argument must be folder";
        ClassPath cp = ClassPath.getClassPath(folder, ClassPath.SOURCE);
        if (cp == null) {
            // see http://www.netbeans.org/issues/show_bug.cgi?id=159228
            throw new IllegalStateException(String.format("No classpath for %s.", folder.getPath())); // NOI18N
        }
        return cp.getResourceName(folder, '.', false);
    }

    /**
     * get package name for given CompilationUnitTree
     *
     * @param unit
     * @return
     */
    public static String getPackageName(CompilationUnitTree unit) {
        assert unit != null;
        ExpressionTree name = unit.getPackageName();
        if (name == null) {
            //default package
            return "";
        }
        return name.toString();
    }

    /**
     * get package name for given url.
     *
     * @param url
     * @return
     */
    public static String getPackageName(URL url) {
        File f = null;
        try {
            f = FileUtil.normalizeFile(Utilities.toFile(url.toURI()));
        } catch (URISyntaxException ex) {
            throw new IllegalArgumentException(ex);
        }
        String suffix = "";

        do {
            FileObject fo = FileUtil.toFileObject(f);
            if (fo != null) {
                if ("".equals(suffix)) {
                    return getPackageName(fo);
                }
                String prefix = getPackageName(fo);
                return prefix + ("".equals(prefix) ? "" : ".") + suffix; // NOI18N
            }
            if (!"".equals(suffix)) {
                suffix = "." + suffix; // NOI18N
            }
            suffix = f.getPath().substring(f.getPath().lastIndexOf(File.separatorChar) + 1) + suffix; // NOI18N
            f = f.getParentFile();
        } while (f != null);
        throw new IllegalArgumentException("Cannot create package name for url " + url); // NOI18N
    }

    /**
     * creates or finds FileObject according to
     *
     * @param url
     * @return FileObject
     */
    public static FileObject getOrCreateFolder(URL url) throws IOException {
        try {
            FileObject result = URLMapper.findFileObject(url);
            if (result != null) {
                return result;
            }
            File f = new File(url.toURI());

            result = FileUtil.createFolder(f);
            return result;
        } catch (URISyntaxException ex) {
            throw new IOException(ex);
        }
    }

    /**
     *
     * @param url
     * @return
     * @throws IOException
     */
    public static FileObject getClassPathRoot(URL url) throws IOException {
        FileObject result = getRootFileObject(url);
        if(result == null) {
            return null;
        }
        ClassPath classPath = ClassPath.getClassPath(result, ClassPath.SOURCE);
        if(classPath == null) {
            return null;
        }
        return classPath.findOwnerRoot(result);
    }

    /**
     * Get all supertypes for given type
     *
     * @param type
     * @param info
     * @return
     * @deprecated 
     */
    @Deprecated
    public static Collection<TypeElement> getSuperTypes(TypeElement type, CompilationInfo info) {
        Collection<TypeElement> result = new HashSet<TypeElement>();
        LinkedList<TypeElement> l = new LinkedList<TypeElement>();
        l.add(type);
        while (!l.isEmpty()) {
            TypeElement t = l.removeFirst();
            TypeElement superClass = typeToElement(t.getSuperclass(), info);
            if (superClass != null) {
                result.add(superClass);
                l.addLast((TypeElement) superClass);
            }
            Collection<TypeElement> interfaces = typesToElements(t.getInterfaces(), info);
            result.addAll(interfaces);
            l.addAll(interfaces);
        }
        return result;
    }

    /**
     * get supertypes of given types
     *
     * @param type
     * @param info
     * @param sourceOnly true if only types defined in open project should be
     * searched
     * @return
     * @deprecated 
     */
    @Deprecated
    public static Collection<TypeElement> getSuperTypes(TypeElement type, CompilationInfo info, boolean sourceOnly) {
        if (!sourceOnly) {
            return getSuperTypes(type, info);
        }
        Collection<TypeElement> result = new HashSet<TypeElement>();
        for (TypeElement el : getSuperTypes(type, info)) {
            ElementHandle<TypeElement> handle = ElementHandle.create(el);
            FileObject file = SourceUtils.getFile(handle, info.getClasspathInfo());
            if (file != null && isFileInOpenProject(file) && !isFromLibrary(handle, info.getClasspathInfo())) {
                result.add(el);
            }
        }
        return result;
    }

    public static TypeElement typeToElement(TypeMirror type, CompilationInfo info) {
        return (TypeElement) info.getTypes().asElement(type);
    }

    private static boolean isOpenProject(Project p) {
        return OpenProjects.getDefault().isProjectOpen(p);
    }

    private static Collection<TypeElement> typesToElements(Collection<? extends TypeMirror> types, CompilationInfo info) {
        Collection<TypeElement> result = new HashSet();
        for (TypeMirror tm : types) {
            result.add(typeToElement(tm, info));
        }
        return result;
    }

    public static Collection<FileObject> elementsToFile(Collection<? extends Element> elements, ClasspathInfo cpInfo) {
        Collection<FileObject> result = new HashSet();
        for (Element handle : elements) {
            result.add(SourceUtils.getFile(handle, cpInfo));
        }
        return result;
    }

    public static boolean elementExistsIn(TypeElement target, Element member, CompilationInfo info) {
        for (Element currentMember : target.getEnclosedElements()) {
            if (currentMember.getKind().equals(member.getKind())
                    && currentMember.getSimpleName().equals(member.getSimpleName())) {
                if (currentMember.getKind() == ElementKind.METHOD) {
                    ExecutableElement exMethod = (ExecutableElement) currentMember;
                    ExecutableElement method = (ExecutableElement) member;
                    if (exMethod.getParameters().size() == method.getParameters().size()) {
                        boolean sameParameters = true;
                        for (int j = 0; j < exMethod.getParameters().size(); j++) {
                            TypeMirror exType = ((VariableElement) exMethod.getParameters().get(j)).asType();
                            TypeMirror paramType = method.getParameters().get(j).asType();
                            if (!info.getTypes().isSameType(exType, paramType)) {
                                sameParameters = false;
                            }
                        }
                        if (sameParameters) {
                            return true;
                        }
                    }
                } else {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param fqn
     * @param info
     * @return
     */
    public static boolean typeExists(String fqn, CompilationInfo info) {
        return info.getElements().getTypeElement(fqn) != null;
    }

    /**
     * create ClasspathInfo for specified files includes dependencies
     *
     * @param files
     * @return
     * @deprecated 
     */
    @Deprecated
    public static ClasspathInfo getClasspathInfoFor(FileObject... files) {
        return getClasspathInfoFor(true, files);
    }

    /**
     * create ClasspathInfo for specified files
     *
     * @param dependencies
     * @param files
     * @return
     */
    public static ClasspathInfo getClasspathInfoFor(boolean dependencies, FileObject... files) {
        return getClasspathInfoFor(dependencies, false, files);
    }

    /**
     * create ClasspathInfo for specified files
     *
     * @param dependencies include dependencies
     * @param backSource libraries replaces by sources using
     * SourceForBinaryQuery
     * @param files
     * @return
     */
    @SuppressWarnings("CollectionContainsUrl")
    public static ClasspathInfo getClasspathInfoFor(boolean dependencies, boolean backSource, FileObject... files) {
        assert files.length > 0;
        Set<URL> dependentSourceRoots = new HashSet();
        Set<URL> dependentCompileRoots = new HashSet();
        ClassPath nullPath = ClassPathSupport.createClassPath(new FileObject[0]);
        ClassPath boot = null;
        ClassPath moduleBoot = null;
        ClassPath compile = null;
        ClassPath moduleCompile = null;
        ClassPath moduleClass = null;        
        for (FileObject fo : files) {
            ClassPath cp = null;
            FileObject ownerRoot = null;
            if (fo != null) {
                cp = ClassPath.getClassPath(fo, ClassPath.SOURCE);
                if (cp != null) {
                    ownerRoot = cp.findOwnerRoot(fo);
                }
            }
            if (cp != null && ownerRoot != null && FileUtil.getArchiveFile(ownerRoot) == null) {
                for (FileObject src : cp.getRoots()) { // Keep all source roots from cp. Needed if project has multiple source roots.
                URL sourceRoot = URLMapper.findURL(src, URLMapper.INTERNAL);
                if (dependencies) {
                    Set<URL> urls = SourceUtils.getDependentRoots(sourceRoot, false);
                    Set<ClassPath> cps = GlobalPathRegistry.getDefault().getPaths(ClassPath.SOURCE);
                    Set<URL> toRetain = new HashSet<URL>();
                    for (ClassPath path : cps) {
                        for (ClassPath.Entry e : path.entries()) {
                            toRetain.add(e.getURL());
                        }
                    }
                    Set<URL> compileUrls = new HashSet<URL>(urls);
                    urls.retainAll(toRetain);
                    compileUrls.removeAll(toRetain);
                    dependentSourceRoots.addAll(urls);
                    dependentCompileRoots.addAll(compileUrls);
                } else {
                    dependentSourceRoots.add(sourceRoot);
                }
                if (FileOwnerQuery.getOwner(fo) != null) {
                    for (FileObject f : cp.getRoots()) {
                        dependentCompileRoots.add(URLMapper.findURL(f, URLMapper.INTERNAL));
                    }
                }
                }
            } else {
                for (ClassPath scp : GlobalPathRegistry.getDefault().getPaths(ClassPath.SOURCE)) {
                    for (FileObject root : scp.getRoots()) {
                        dependentSourceRoots.add(URLMapper.findURL(root, URLMapper.INTERNAL));
                    }
                }
            }
            
            if(fo != null) {
                ClassPath fboot = ClassPath.getClassPath(fo, ClassPath.BOOT);
                ClassPath fmoduleboot = ClassPath.getClassPath(fo, JavaClassPathConstants.MODULE_BOOT_PATH);
                ClassPath fcompile = ClassPath.getClassPath(fo, ClassPath.COMPILE);
                ClassPath fmodulecompile = ClassPath.getClassPath(fo, JavaClassPathConstants.MODULE_COMPILE_PATH);
                ClassPath fmoduleclass = ClassPath.getClassPath(fo, JavaClassPathConstants.MODULE_CLASS_PATH);
                //When file[0] is a class file, there is no compile cp but execute cp
                //try to get it
                if (fcompile == null) {
                    fcompile = ClassPath.getClassPath(fo, ClassPath.EXECUTE);
                }
                //If no cp found at all log the file and use nullPath since the ClasspathInfo.create
                //doesn't accept null compile or boot cp.
                if (fcompile == null) {
                    LOG.log(Level.WARNING, "No classpath for: {0} {1}", new Object[]{FileUtil.getFileDisplayName(fo), FileOwnerQuery.getOwner(fo)}); //NOI18N
                } else {
                    compile = compile != null ? merge(compile, fcompile) : fcompile;
                }
                
                if (fboot != null) {
                    boot = boot != null ? merge(boot, fboot) : fboot;
                }
                if (fmoduleboot != null) {
                    moduleBoot = moduleBoot != null ? merge(moduleBoot, fmoduleboot) : fmoduleboot;
                }
                if (fmodulecompile != null) {
                    moduleCompile = moduleCompile != null ? merge(moduleCompile, fmodulecompile) : fmodulecompile;
                }
                if (fmoduleclass != null) {
                    moduleClass = moduleClass != null ? merge(moduleClass, fmoduleclass) : fmoduleclass;
                }
            }
        }

        if (backSource) {
            for (FileObject file : files) {
                if (file != null) {
                    ClassPath source = ClassPath.getClassPath(file, ClassPath.COMPILE);
                    for (Entry root : source.entries()) {
                        Result r = SourceForBinaryQuery.findSourceRoots(root.getURL());
                        for (FileObject root2 : r.getRoots()) {
                            dependentSourceRoots.add(URLMapper.findURL(root2, URLMapper.INTERNAL));
                        }
                    }
                }
            }
        }

        ClassPath rcp = ClassPathSupport.createClassPath(dependentSourceRoots.toArray(new URL[dependentSourceRoots.size()]));
        if (compile == null) {
            compile = nullPath;
        }
        compile = merge(compile, ClassPathSupport.createClassPath(dependentCompileRoots.toArray(new URL[dependentCompileRoots.size()])));
        if (boot == null) {
            boot = JavaPlatform.getDefault().getBootstrapLibraries();
        }
        return new ClasspathInfo.Builder(boot == null ? nullPath : boot)
                .setModuleBootPath(moduleBoot == null ? boot == null? nullPath : boot : moduleBoot)
                .setClassPath(compile)
                .setModuleCompilePath(moduleCompile)
                .setModuleClassPath(moduleClass)
                .setSourcePath(rcp).
                build();
    }


    /**
     * @param handle
     * @return  
     */
    public static FileObject getFileObject(TreePathHandle handle) {
        ElementHandle elementHandle = handle.getElementHandle();
        if (elementHandle == null ) {
            return handle.getFileObject();
        }
       ClasspathInfo info = getClasspathInfoFor(false, handle.getFileObject()); 
       return SourceUtils.getFile(elementHandle, info);
    }    
    /**
     * create ClasspathInfo for specified handles
     *
     * @param handles
     * @return
     */
    public static ClasspathInfo getClasspathInfoFor(TreePathHandle... handles) {
        FileObject[] result = new FileObject[handles.length];
        int i = 0;
        for (TreePathHandle handle : handles) {
            FileObject fo = getFileObject(handle);
            if (i == 0 && fo == null) {
                result = new FileObject[handles.length + 1];
                result[i++] = handle.getFileObject();
            }
            result[i++] = fo;
        }
        return getClasspathInfoFor(result);
    }

    /**
     * Finds type parameters from
     * <code>typeArgs</code> list that are referenced by
     * <code>tm</code> type.
     *
     * @param utils compilation type utils
     * @param typeArgs modifiable list of type parameters to search; found types
     * will be removed (performance reasons).
     * @param result modifiable list that will contain referenced type
     * parameters
     * @param tm parametrized type to analyze
     */
    public static void findUsedGenericTypes(Types utils, List<TypeMirror> typeArgs, Set<TypeMirror> result, TypeMirror tm) {
        if (typeArgs.isEmpty()) {
            return;
        } else if (tm.getKind() == TypeKind.TYPEVAR) {
            TypeVariable type = (TypeVariable) tm;
            int index = findTypeIndex(utils, typeArgs, type);
            if (index >= 0) {
                result.add(typeArgs.get(index));
            } else {
                TypeMirror low = type.getLowerBound();
                if (low != null && low.getKind() != TypeKind.NULL) {
                    findUsedGenericTypes(utils, typeArgs, result, low);
                }
                TypeMirror up = type.getUpperBound();
                if (up != null) {
                    findUsedGenericTypes(utils, typeArgs, result, up);
                }
                int idx = findTypeIndex(utils, typeArgs, type);
                if (idx >= 0) {
                    result.add(typeArgs.get(idx));
                }
            }
        } else if (tm.getKind() == TypeKind.DECLARED) {
            DeclaredType type = (DeclaredType) tm;
            for (TypeMirror tp : type.getTypeArguments()) {
                findUsedGenericTypes(utils, typeArgs, result, tp);
            }
        } else if (tm.getKind() == TypeKind.WILDCARD) {
            WildcardType type = (WildcardType) tm;
            TypeMirror ex = type.getExtendsBound();
            if (ex != null) {
                findUsedGenericTypes(utils, typeArgs, result, ex);
            }
            TypeMirror su = type.getSuperBound();
            if (su != null) {
                findUsedGenericTypes(utils, typeArgs, result, su);
            }
        }
    }

    private static int findTypeIndex(Types utils, List<TypeMirror> typeArgs, TypeMirror type) {
        int i = -1;
        for (TypeMirror typeArg : typeArgs) {
            i++;
            if (utils.isSameType(type, typeArg)) {
                return i;
            }
        }
        return -1;
    }

    public static List<TypeMirror> filterTypes(List<TypeMirror> source, Set<TypeMirror> used) {
        List<TypeMirror> result = new ArrayList<TypeMirror>(source.size());

        for (TypeMirror tm : source) {
            if (used.contains(tm)) {
                result.add(tm);
            }
        }

        return result;
    }

    /**
     * translates list of elements to list of types
     *
     * @param typeParams elements
     * @return types
     * @deprecated 
     */
    @Deprecated
    public static List<TypeMirror> resolveTypeParamsAsTypes(List<? extends Element> typeParams) {
        if (typeParams.isEmpty()) {
            return Collections.<TypeMirror>emptyList();
        }
        List<TypeMirror> typeArgs = new ArrayList<TypeMirror>(typeParams.size());
        for (Element elm : typeParams) {
            typeArgs.add(elm.asType());
        }
        return typeArgs;
    }

    /**
     * finds the nearest enclosing ClassTree on
     * <code>path</code> that is class or interface or enum or annotation type
     * and is or is not annonymous. In case no ClassTree is found the first top
     * level ClassTree is returned.
     *
     * Especially useful for selecting proper tree to refactor.
     *
     * @param javac javac
     * @param path path to search
     * @param isClass stop on class
     * @param isInterface stop on interface
     * @param isEnum stop on enum
     * @param isAnnotation stop on annotation type
     * @param isAnonymous check if class or interface is annonymous
     * @return path to the enclosing ClassTree
     * @deprecated 
     */
    @Deprecated
    public static @NullUnknown
    TreePath findEnclosingClass(CompilationInfo javac, TreePath path, boolean isClass, boolean isInterface, boolean isEnum, boolean isAnnotation, boolean isAnonymous) {
        if (path == null) {
            return null;
        }
        Tree selectedTree = path.getLeaf();
        TreeUtilities utils = javac.getTreeUtilities();
        while (true) {
            if (TreeUtilities.CLASS_TREE_KINDS.contains(selectedTree.getKind())) {
                ClassTree classTree = (ClassTree) selectedTree;
                if (isEnum && utils.isEnum(classTree)
                        || isInterface && utils.isInterface(classTree)
                        || isAnnotation && utils.isAnnotation(classTree)
                        || isClass && !(utils.isInterface(classTree) || utils.isEnum(classTree) || utils.isAnnotation(classTree))) {

                    Tree.Kind parentKind = path.getParentPath().getLeaf().getKind();
                    if (isAnonymous || Tree.Kind.NEW_CLASS != parentKind) {
                        break;
                    }
                }
            }

            path = path.getParentPath();
            if (path == null) {
                List<? extends Tree> typeDecls = javac.getCompilationUnit().getTypeDecls();
                if (typeDecls.isEmpty()) {
                    return null;
                }
                selectedTree = typeDecls.get(0);
                if (selectedTree.getKind().asInterface() == ClassTree.class) {
                    return javac.getTrees().getPath(javac.getCompilationUnit(), selectedTree);
                } else {
                    return null;
                }
            }
            selectedTree = path.getLeaf();
        }
        return path;
    }

    //XXX: copied from SourceUtils.addImports. Ideally, should be on one place only:
    public static CompilationUnitTree addImports(CompilationUnitTree cut, List<String> toImport, TreeMaker make)
            throws IOException {
        // do not modify the list given by the caller (may be reused or immutable).
        toImport = new ArrayList<String>(toImport);
        Collections.sort(toImport);

        List<ImportTree> imports = new ArrayList<ImportTree>(cut.getImports());
        int currentToImport = toImport.size() - 1;
        int currentExisting = imports.size() - 1;

        while (currentToImport >= 0 && currentExisting >= 0) {
            String currentToImportText = toImport.get(currentToImport);

            while (currentExisting >= 0 && (imports.get(currentExisting).isStatic() || imports.get(currentExisting).getQualifiedIdentifier().toString().compareTo(currentToImportText) > 0)) {
                currentExisting--;
            }

            if (currentExisting >= 0) {
                imports.add(currentExisting + 1, make.Import(make.Identifier(currentToImportText), false));
                currentToImport--;
            }
        }
        // we are at the head of import section and we still have some imports
        // to add, put them to the very beginning
        while (currentToImport >= 0) {
            String importText = toImport.get(currentToImport);
            imports.add(0, make.Import(make.Identifier(importText), false));
            currentToImport--;
        }
        // return a copy of the unit with changed imports section
        return make.CompilationUnit(cut.getPackageAnnotations(), cut.getPackageName(), imports, cut.getTypeDecls(), cut.getSourceFile());
    }

    /**
     * transforms passed modifiers to abstract form
     *
     * @param make a tree maker
     * @param oldMods modifiers of method or class
     * @return the abstract form of ModifiersTree
     */
    public static ModifiersTree makeAbstract(TreeMaker make, ModifiersTree oldMods) {
        if (oldMods.getFlags().contains(Modifier.ABSTRACT)) {
            return oldMods;
        }
        Set<Modifier> flags = new HashSet<Modifier>(oldMods.getFlags());
        flags.add(Modifier.ABSTRACT);
        flags.remove(Modifier.FINAL);
        return make.Modifiers(flags, oldMods.getAnnotations());
    }

    public static String variableClashes(String newName, TreePath tp, CompilationInfo info) {
        LocalVarScanner lookup = new LocalVarScanner(info, newName);
        TreePath scopeBlok = tp;
        EnumSet set = EnumSet.of(Tree.Kind.BLOCK, Tree.Kind.FOR_LOOP, Tree.Kind.METHOD);
        while (scopeBlok != null && !set.contains(scopeBlok.getLeaf().getKind())) {
            scopeBlok = scopeBlok.getParentPath();
        }
        if(scopeBlok == null) {
            return null;
        }
        Element var = info.getTrees().getElement(tp);
        if (var != null) {
            lookup.scan(scopeBlok, var);
        }

        if (lookup.hasRefernces()) {
            return NbBundle.getMessage(RefactoringUtils.class, "MSG_LocVariableClash",newName);
        }

        TreePath temp = tp;
        while (temp != null && temp.getLeaf().getKind() != Tree.Kind.METHOD) {
            Scope scope = info.getTrees().getScope(temp);
            for (Element el : scope.getLocalElements()) {
                if (el.getSimpleName().toString().equals(newName)) {
                    return NbBundle.getMessage(RefactoringUtils.class, "MSG_LocVariableClash",newName);
                }
            }
            temp = temp.getParentPath();
        }
        return null;
    }

    public static boolean isSetter(CompilationInfo info, ExecutableElement el, Element propertyElement) {
        CodeStyle codeStyle = getCodeStyle(info);
        String setterName = CodeStyleUtils.computeSetterName(
                propertyElement.getSimpleName(),
                propertyElement.getModifiers().contains(Modifier.STATIC),
                codeStyle);

        return el.getSimpleName().contentEquals(setterName)
                && el.getReturnType().getKind() == TypeKind.VOID
                && el.getParameters().size() == 1
                && info.getTypes().isSameType(el.getParameters().iterator().next().asType(), propertyElement.asType());
    }

    public static boolean isGetter(CompilationInfo info, ExecutableElement el, Element propertyElement) {
        CodeStyle codeStyle = getCodeStyle(info);
        String getterName = CodeStyleUtils.computeGetterName(
                propertyElement.getSimpleName(),
                propertyElement.asType().getKind() == TypeKind.BOOLEAN,
                propertyElement.getModifiers().contains(Modifier.STATIC),
                codeStyle);
        return el.getSimpleName().contentEquals(getterName)
                && info.getTypes().isSameType(el.getReturnType(),propertyElement.asType())
                && el.getParameters().isEmpty();
    }
    
    public static String removeFieldPrefixSuffix(Element var, CodeStyle cs) {
        boolean isStatic = var.getModifiers().contains(Modifier.STATIC);
        return CodeStyleUtils.removePrefixSuffix(var.getSimpleName(),
                isStatic ? cs.getStaticFieldNamePrefix() : cs.getFieldNamePrefix(),
                isStatic ? cs.getStaticFieldNameSuffix() : cs.getFieldNameSuffix());
    }
    
    public static String addParamPrefixSuffix(CharSequence name, CodeStyle cs) {
        return CodeStyleUtils.addPrefixSuffix(name,
                cs.getParameterNamePrefix(),
                cs.getParameterNameSuffix());
    }

    public static String getTestMethodName(String propertyName) {
	return "test" + CodeStyleUtils.getCapitalizedName(propertyName); //NOI18N
    }

    public static boolean isWeakerAccess(Set<Modifier> modifiers, Set<Modifier> modifiers0) {
        return accessLevel(modifiers) < accessLevel(modifiers0);
    }

    private static int accessLevel(Set<Modifier> modifiers) {
        if (modifiers.contains(Modifier.PRIVATE)) {
            return 0;
        }
        if (modifiers.contains(Modifier.PROTECTED)) {
            return 2;
        }
        if (modifiers.contains(Modifier.PUBLIC)) {
            return 3;
        }
        return 1;
    }

    public static String getAccess(Set<Modifier> modifiers) {
        if (modifiers.contains(Modifier.PRIVATE)) {
            return "private"; //NOI18N
        }
        if (modifiers.contains(Modifier.PROTECTED)) {
            return "protected"; //NOI18N
        }
        if (modifiers.contains(Modifier.PUBLIC)) {
            return "public"; //NOI18N
        }
        return "<default>"; //NOI18N
    }

    public static boolean isFromTestRoot(FileObject file, ClassPath cp) {
        boolean inTest = false;
        if (cp != null) {
            FileObject root = cp.findOwnerRoot(file);
            if (root != null && UnitTestForSourceQuery.findSources(root).length > 0) {
                inTest = true;
            }
        }
        return inTest;
    }

    public static FileObject getRootFileObject(URL url) throws IOException {
        FileObject result = URLMapper.findFileObject(url);
        File f;
        try {
            f = result != null ? null : FileUtil.normalizeFile(Utilities.toFile(url.toURI())); //NOI18N
        } catch (URISyntaxException ex) {
            throw new IOException(ex);
        }
        while (result == null && f != null) {
            result = FileUtil.toFileObject(f);
            f = f.getParentFile();
        }
        return result;
    }

    @SuppressWarnings("CollectionContainsUrl")
    public static ClassPath merge(final ClassPath... cps) {
        final Set<URL> roots = new LinkedHashSet<URL>(cps.length);
        for (final ClassPath cp : cps) {
            if (cp != null) {
                for (final ClassPath.Entry entry : cp.entries()) {
                    final URL root = entry.getURL();
                    if (!roots.contains(root)) {
                        roots.add(root);
                    }
                }
            }
        }
        return ClassPathSupport.createClassPath(roots.toArray(new URL[roots.size()]));
    }

    public static boolean isFromEditor(EditorCookie ec) {
        if (ec != null && NbDocument.findRecentEditorPane(ec) != null) {
            TopComponent activetc = TopComponent.getRegistry().getActivated();
            if (activetc instanceof CloneableEditorSupport.Pane) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Checks if the element is a method or constructor. Returns {@code false} for {@code null} input.
     * @param e element to check
     * @return true iff the element is a constructor or method.
     */
    public static boolean isExecutableElement(Element e) {
        if (e == null) {
            return false;
        }
        ElementKind ek = e.getKind();
        return ek == ElementKind.CONSTRUCTOR || ek == ElementKind.METHOD;
    }

    private RefactoringUtils() {
    }
}
