/*
 * 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.*;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.util.*;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;
import org.netbeans.api.java.classpath.ClassPath;
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.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.api.ExtractSuperclassRefactoring;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.api.MemberInfo;
import org.netbeans.modules.refactoring.java.spi.JavaRefactoringPlugin;
import org.netbeans.modules.refactoring.java.spi.RefactoringVisitor;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;

/** Plugin that implements the core functionality of Extract Super Class refactoring.
 *
 * @author Martin Matula, Jan Pokorsky
 */
public final class ExtractSuperclassRefactoringPlugin extends JavaRefactoringPlugin {
    /** Reference to the parent refactoring instance */
    private final ExtractSuperclassRefactoring refactoring;
    
    /** source class */
    private ElementHandle<TypeElement> classHandle;
        
    private String pkgName;

    /** Creates a new instance of ExtractSuperClassRefactoringPlugin
     * @param refactoring Parent refactoring instance.
     */
    ExtractSuperclassRefactoringPlugin(ExtractSuperclassRefactoring refactoring) {
        this.refactoring = refactoring;
    }

    @Override
    protected JavaSource getJavaSource(Phase p) {
        return JavaSource.forFileObject(refactoring.getSourceType().getFileObject());
    }

    @Override
    protected Problem preCheck(CompilationController javac) throws IOException {
        // fire operation start on the registered progress listeners (2 step)
        fireProgressListenerStart(AbstractRefactoring.PRE_CHECK, 2);
        javac.toPhase(JavaSource.Phase.RESOLVED);
        try {
            TreePathHandle sourceType = refactoring.getSourceType();
            
            // check whether the element is valid
            Problem result = isElementAvail(sourceType, javac);
            if (result != null) {
                // fatal error -> don't continue with further checks
                return result;
            }
            
            // check whether the element is an unresolved class
            Element sourceElm = sourceType.resolveElement(javac);
            result = JavaPluginUtils.isSourceElement(sourceElm, javac);
            if (result != null) {
                return result;
            }
            if (sourceElm == null) {
                // fatal error -> return
                return new Problem(true, NbBundle.getMessage(ExtractSuperclassRefactoringPlugin.class, "ERR_ElementNotAvailable")); // NOI18N
            }

            if (sourceElm.getKind() != ElementKind.CLASS) {
                return new Problem(true, NbBundle.getMessage(ExtractSuperclassRefactoringPlugin.class, "ERR_ExtractSC_MustBeClass"));
            }
            
            classHandle = ElementHandle.<TypeElement>create((TypeElement) sourceElm);
            
            PackageElement pkgElm = (PackageElement) javac.getElementUtilities().outermostTypeElement(sourceElm).getEnclosingElement();
            pkgName = pkgElm.getQualifiedName().toString();
            
            // increase progress (step 1)
            fireProgressListenerStep();
            
            // all checks passed -> return null
            return null;
        } finally {
            // fire operation end on the registered progress listeners
            fireProgressListenerStop();
        }
    }
    
    @Override
    public Problem fastCheckParameters() {
        Problem result = null;
        
        String newName = refactoring.getSuperClassName();
        
        if (!Utilities.isJavaIdentifier(newName)) {
            result = createProblem(result, true, NbBundle.getMessage(ExtractSuperclassRefactoringPlugin.class, "ERR_InvalidIdentifier", newName)); // NOI18N
            return result;
        }
        
        FileObject primFile = refactoring.getSourceType().getFileObject();
        FileObject folder = primFile.getParent();
        FileObject[] children = folder.getChildren();
        for (FileObject child: children) {
            if (!child.isVirtual() && child.getName().equals(newName) && "java".equals(child.getExt())) { // NOI18N
                result = createProblem(result, true, NbBundle.getMessage(ExtractSuperclassRefactoringPlugin.class, "ERR_ClassClash", newName, pkgName)); // NOI18N
                return result;
            }
        }

        return super.fastCheckParameters();
    }

    @Override
    protected Problem fastCheckParameters(CompilationController javac) throws IOException {
        Problem result = null;
        String newName = refactoring.getSuperClassName();
        TypeMirror parsedType = javac.getTreeUtilities().parseType(newName, classHandle.resolve(javac));
        if(parsedType != null && parsedType.getKind() != TypeKind.ERROR) {
            result = createProblem(result, true, NbBundle.getMessage(ExtractInterfaceRefactoringPlugin.class, "ERR_ClassClash", newName, pkgName)); // NOI18N
            return result;
        }
        return super.fastCheckParameters(javac);
    }

    @Override
    public Problem checkParameters() {
        MemberInfo[] members = refactoring.getMembers();
        if (members.length == 0) {
            return new Problem(true, NbBundle.getMessage(ExtractSuperclassRefactoringPlugin.class, "ERR_ExtractSuperClass_MembersNotAvailable")); // NOI18N);
        }
        return super.checkParameters();

    }
    
    @Override
    protected Problem checkParameters(CompilationController javac) throws IOException {
        javac.toPhase(JavaSource.Phase.RESOLVED);
        
        TypeElement sourceType = (TypeElement) refactoring.getSourceType().resolveElement(javac);
        assert sourceType != null;
        
        Set<? extends Element> members = new HashSet<Element>(sourceType.getEnclosedElements());
        
        fireProgressListenerStart(AbstractRefactoring.PARAMETERS_CHECK, refactoring.getMembers().length);
        try {
            for (MemberInfo info : refactoring.getMembers()) {
                Problem p = null;
                switch(info.getGroup()) {
                case FIELD:
                    @SuppressWarnings("unchecked")
                    ElementHandle<VariableElement> vehandle = (ElementHandle<VariableElement>) info.getElementHandle();
                    VariableElement field = vehandle.resolve(javac);
                    p = checkFieldParameter(javac, field, members);
                    break;
                case METHOD:
                    @SuppressWarnings("unchecked")
                    ElementHandle<ExecutableElement> eehandle = (ElementHandle<ExecutableElement>) info.getElementHandle();
                    ExecutableElement method = eehandle.resolve(javac);
                    p = checkMethodParameter(javac, method, members);
                    break;
                }

                if (p != null) {
                    return p;
                }
                
                fireProgressListenerStep();
            }
        } finally {
            fireProgressListenerStop();
        }

        // XXX check refactoring.getImplements()

        return null;
    }
    
    private Problem checkFieldParameter(CompilationController javac, VariableElement elm, Set<? extends Element> members) throws IOException {
        if (elm == null) {
            return new Problem(true, NbBundle.getMessage(ExtractSuperclassRefactoringPlugin.class, "ERR_ElementNotAvailable")); // NOI18N
        }
        if (javac.getElementUtilities().isSynthetic(elm) || elm.getKind() != ElementKind.FIELD) {
            return new Problem(true, NbBundle.getMessage(ExtractInterfaceRefactoringPlugin.class, "ERR_ExtractSuperClass_UnknownMember", // NOI18N
                    elm.toString()));
        }
        if (!members.contains(elm)) {
            return new Problem(true, NbBundle.getMessage(ExtractInterfaceRefactoringPlugin.class, "ERR_ExtractSuperClass_UnknownMember", // NOI18N
                    elm.toString()));
        }
//        Set<Modifier> mods = elm.getModifiers();
//        if (mods.contains(Modifier.PUBLIC) && mods.contains(Modifier.STATIC) && mods.contains(Modifier.FINAL)) {
//            VariableTree tree = (VariableTree) javac.getTrees().getTree(elm);
//            if (tree.getInitializer() != null) {
//                continue;
//            }
//        }
//        return new Problem(true, NbBundle.getMessage(ExtractInterfaceRefactoringPlugin.class, "ERR_ExtractInterface_WrongModifiers", elm.getSimpleName().toString())); // NOI18N
        return null;
    }
    
    private Problem checkMethodParameter(CompilationController javac, ExecutableElement elm, Set<? extends Element> members) throws IOException {
        if (elm == null) {
            return new Problem(true, NbBundle.getMessage(ExtractSuperclassRefactoringPlugin.class, "ERR_ElementNotAvailable")); // NOI18N
        }
        if (javac.getElementUtilities().isSynthetic(elm) || elm.getKind() != ElementKind.METHOD) {
            return new Problem(true, NbBundle.getMessage(ExtractInterfaceRefactoringPlugin.class, "ERR_ExtractSuperClass_UnknownMember", // NOI18N
                    elm.toString()));
        }
        if (!members.contains(elm)) {
            return new Problem(true, NbBundle.getMessage(ExtractInterfaceRefactoringPlugin.class, "ERR_ExtractSuperClass_UnknownMember", // NOI18N
                    elm.toString()));
        }
//        Set<Modifier> mods = elm.getModifiers();
//        if (!mods.contains(Modifier.PUBLIC) || mods.contains(Modifier.STATIC)) {
//            return new Problem(true, NbBundle.getMessage(ExtractInterfaceRefactoringPlugin.class, "ERR_ExtractInterface_WrongModifiers", elm.getSimpleName().toString())); // NOI18N
//        }
        return null;
        
    }

    private Set<FileObject> getRelevantFiles() {
        final Set<FileObject> set = new HashSet<FileObject>();
        set.add(refactoring.getSourceType().getFileObject());
        return set;
    }
    
    private ClasspathInfo getClasspathInfo(Set<FileObject> a) {
        ClasspathInfo cpInfo;
        cpInfo = JavaRefactoringUtils.getClasspathInfoFor(a.toArray(new FileObject[a.size()]));
        return cpInfo;
    }
    
    @Override
    public Problem prepare(RefactoringElementsBag bag) {
        RefactoringVisitor visitor = new ExtractSuperclassTransformer(refactoring, classHandle);
        Set<FileObject> a = getRelevantFiles();
        fireProgressListenerStart(AbstractRefactoring.PREPARE, a.size());
        TransformTask transform = new TransformTask(visitor, refactoring.getSourceType());
        Problem problem = createAndAddElements(a, transform, bag, refactoring, getClasspathInfo(a));
        fireProgressListenerStop();
        return problem;
    }
    
    private final static class ExtractSuperclassTransformer extends RefactoringVisitor {
        private final ExtractSuperclassRefactoring refactoring;
        private final ElementHandle<TypeElement> sourceType;
        private List<Tree> members;
        private boolean makeAbstract;
        private List<Tree> members2Remove;
        
        private ExtractSuperclassTransformer(ExtractSuperclassRefactoring refactoring, ElementHandle<TypeElement> sourceType) {
            this.sourceType = sourceType;
            this.refactoring = refactoring;
        }
        
        @Override
        public Tree visitClass(ClassTree classTree, Element p) {
            TypeElement clazz = this.sourceType.resolve(workingCopy);
            assert clazz != null;
            Element current = workingCopy.getTrees().getElement(getCurrentPath());
            if(current == clazz) {
                GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
                makeAbstract = false;
                members = new LinkedList<Tree>();
                members2Remove = new LinkedList<Tree>();
                addConstructors(clazz);
                super.visitClass(classTree, p);

                List<Tree> implementsList = new ArrayList<Tree>();
                for (MemberInfo/*<ElementHandle<? extends Element>>*/ member : refactoring.getMembers()) {
                    if (member.getGroup() == MemberInfo.Group.IMPLEMENTS) {
                        TypeMirrorHandle handle = (TypeMirrorHandle) member.getElementHandle();
                        // XXX check if interface is not aready there; the templates might be changed by user :-(
                        TypeMirror implMirror = handle.resolve(workingCopy);
                        implementsList.add(make.Type(implMirror));
                        // XXX needs more granular check
                        makeAbstract |= true;
                    }
                }
                DeclaredType supType = (DeclaredType) clazz.getSuperclass();
                TypeElement supEl = (TypeElement) supType.asElement();
                Tree superClass = supEl.getSuperclass().getKind() == TypeKind.NONE
                        ? null
                        : make.Type(supType);
                makeAbstract |= supEl.getModifiers().contains(Modifier.ABSTRACT);
                ModifiersTree classModifiersTree = make.Modifiers(makeAbstract ? EnumSet.of(Modifier.PUBLIC, Modifier.ABSTRACT) : EnumSet.of(Modifier.PUBLIC));
                final List<? extends TypeMirror> typeParams = findUsedGenericTypes(clazz);
                List<TypeParameterTree> newTypeParams = new ArrayList<TypeParameterTree>(typeParams.size());
                for (TypeParameterElement typeParam : clazz.getTypeParameters()) {
                    TypeMirror origParam = typeParam.asType();
                    for (TypeMirror newParam : typeParams) {
                        if (workingCopy.getTypes().isSameType(origParam, newParam)) {
                            Tree t = workingCopy.getTrees().getTree(typeParam);
                            if (t.getKind() == Tree.Kind.TYPE_PARAMETER) {
                                TypeParameterTree typeParamTree = (TypeParameterTree) t;
                                if (!typeParamTree.getBounds().isEmpty()) {
                                    typeParamTree = (TypeParameterTree) genUtils.importFQNs(t);
                                }
                                newTypeParams.add(typeParamTree);
                            }
                        }
                    }
                }
                ClassTree newClassTree = make.Class(
                        classModifiersTree,
                        refactoring.getSuperClassName(),
                        newTypeParams,
                        superClass,
                        implementsList,
                        Collections.<Tree>emptyList());
                newClassTree = GeneratorUtilities.get(workingCopy).insertClassMembers(newClassTree, members);

                FileObject fileObject = refactoring.getSourceType().getFileObject();
                FileObject sourceRoot = ClassPath.getClassPath(fileObject, ClassPath.SOURCE).findOwnerRoot(fileObject);
                String relativePath = FileUtil.getRelativePath(sourceRoot, fileObject.getParent()) + "/" + refactoring.getSuperClassName() + ".java";
                CompilationUnitTree cu = JavaPluginUtils.createCompilationUnit(sourceRoot, relativePath, newClassTree, workingCopy, make);
                rewrite(null, cu);
                
                // fake interface since interface file does not exist yet
                Tree superClassTree;
                if (typeParams.isEmpty()) {
                    superClassTree = make.Identifier(refactoring.getSuperClassName());
                } else {
                    List<ExpressionTree> typeParamTrees = new ArrayList<ExpressionTree>(typeParams.size());
                    for (TypeMirror typeParam : typeParams) {
                        Tree t = make.Type(typeParam);
                        typeParamTrees.add((ExpressionTree) t);
                    }
                    superClassTree = make.ParameterizedType(
                            make.Identifier(refactoring.getSuperClassName()),
                            typeParamTrees
                            );
                }
                Set<Tree> interfaces2Remove = new HashSet<Tree>();

                interfaces2Remove.addAll(getImplements2Remove(workingCopy, refactoring.getMembers(), clazz));

                // filter out obsolete members
                List<Tree> newMembers = new ArrayList<Tree>();
                for (Tree tree : classTree.getMembers()) {
                    if (!members2Remove.contains(tree)) {
                        newMembers.add(tree);
                    }
                }
                // filter out obsolete implements trees
                List<Tree> newImpls = resolveImplements(classTree.getImplementsClause(), interfaces2Remove);

                ClassTree nc;
                nc = make.Class(
                        classTree.getModifiers(),
                        classTree.getSimpleName(),
                        classTree.getTypeParameters(),
                        superClassTree,
                        newImpls,
                        newMembers);

                rewrite(classTree, nc);
                return classTree;
            }
            return super.visitClass(classTree, p);
        }

        @Override
        public Tree visitVariable(VariableTree variableTree, Element p) {
            Element current = workingCopy.getTrees().getElement(getCurrentPath());
            if (current != null) {
                for (MemberInfo<ElementHandle<? extends Element>> memberInfo : refactoring.getMembers()) {
                    if (memberInfo.getGroup() == MemberInfo.Group.FIELD
                            && memberInfo.getElementHandle().resolve(workingCopy) == current) {
                        GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
                        members2Remove.add(variableTree);
                        VariableTree copy = genUtils.importComments(variableTree, workingCopy.getCompilationUnit());
                        copy = genUtils.importFQNs(copy);
                        ModifiersTree modifiers = copy.getModifiers();
                        if (modifiers.getFlags().contains(Modifier.PRIVATE)) {
                            modifiers = make.removeModifiersModifier(modifiers, Modifier.PRIVATE);
                            modifiers = make.addModifiersModifier(modifiers, Modifier.PROTECTED);
                            copy = make.Variable(modifiers, copy.getName(), copy.getType(), copy.getInitializer());
                            genUtils.copyComments(variableTree, copy, false);
                            genUtils.copyComments(variableTree, copy, true);
                        }
                        members.add(copy);
                        break;
                    }
                }
            }
            return variableTree;
        }

        @Override
        public Tree visitMethod(final MethodTree methodTree, Element p) {
            final Trees trees = workingCopy.getTrees();
            Element current = trees.getElement(getCurrentPath());
            if (current != null) {
                for (MemberInfo<ElementHandle<? extends Element>> memberInfo : refactoring.getMembers()) {
                    if (memberInfo.getGroup() == MemberInfo.Group.METHOD
                            && memberInfo.getElementHandle().resolve(workingCopy) == current) {
                        if(!memberInfo.isMakeAbstract()) {
                            members2Remove.add(methodTree);
                        }
                        GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
                        MethodTree newMethod = genUtils.importComments(methodTree, workingCopy.getCompilationUnit());
                        ModifiersTree modifiers = methodTree.getModifiers();
                        if (modifiers.getFlags().contains(Modifier.PRIVATE)) {
                            modifiers = make.removeModifiersModifier(modifiers, Modifier.PRIVATE);
                            modifiers = make.addModifiersModifier(modifiers, Modifier.PROTECTED);
                        }
                        newMethod = genUtils.importFQNs(newMethod);
                        modifiers = genUtils.importFQNs(modifiers);
                        final List<? extends TypeMirror> thrownTypes = ((ExecutableElement)current).getThrownTypes();
                        List<ExpressionTree> newThrownTypes = new ArrayList<ExpressionTree>(thrownTypes.size());
                        for (TypeMirror typeMirror : thrownTypes) {
                            newThrownTypes.add((ExpressionTree) make.Type(typeMirror)); // Necessary as this is not covered by importFQNs
                        }
                        if (memberInfo.isMakeAbstract() && !current.getModifiers().contains(Modifier.ABSTRACT)) {
                            newMethod = make.Method(
                                    RefactoringUtils.makeAbstract(make, modifiers),
                                    newMethod.getName(),
                                    newMethod.getReturnType(),
                                    newMethod.getTypeParameters(),
                                    newMethod.getParameters(),
                                    newThrownTypes,
                                    (BlockTree) null,
                                    null);
                        } else {
                            newMethod = make.Method(modifiers,
                                    newMethod.getName(),
                                    newMethod.getReturnType(),
                                    newMethod.getTypeParameters(),
                                    newMethod.getParameters(),
                                    newThrownTypes,
                                    newMethod.getBody(),
                                    (ExpressionTree) newMethod.getDefaultValue());
                        }
                        genUtils.copyComments(methodTree, newMethod, false);
                        genUtils.copyComments(methodTree, newMethod, true);
                        makeAbstract |= newMethod.getModifiers().getFlags().contains(Modifier.ABSTRACT);
                        members.add(newMethod);
                        break;
                    }
                }
            }
            return methodTree;
        }

        private List<Tree> getImplements2Remove(CompilationInfo javac,MemberInfo[] members, TypeElement clazz) {
            if (members == null || members.length == 0) {
                return Collections.<Tree>emptyList();
            }
            
            // resolve members to remove
            List<TypeMirror> memberTypes = new ArrayList<TypeMirror>(members.length);
            for (MemberInfo member : members) {
                if (member.getGroup() == MemberInfo.Group.IMPLEMENTS) {
                    TypeMirrorHandle handle = (TypeMirrorHandle) member.getElementHandle();
                    TypeMirror tm = handle.resolve(javac);
                    memberTypes.add(tm);
                }
            }
            
            ClassTree classTree = javac.getTrees().getTree(clazz);
            List<Tree> result = new ArrayList<Tree>();
            Types types = javac.getTypes();
            
            // map TypeMirror to Tree
            for (Tree tree : classTree.getImplementsClause()) {
                TreePath path = javac.getTrees().getPath(javac.getCompilationUnit(), tree);
                TypeMirror existingTM = javac.getTrees().getTypeMirror(path);
                
                for (TypeMirror tm : memberTypes) {
                    if (types.isSameType(tm, existingTM)) {
                        result.add(tree);
                        break;
                    }
                }
            }
            
            return result;
        }
        private static List<Tree> resolveImplements(List<? extends Tree> allImpls, Set<Tree> impls2Remove) {
            List<Tree> ret;
            if (allImpls == null) {
                ret = new ArrayList<Tree>(1);
            } else {
                ret = new ArrayList<Tree>(allImpls.size() + 1);
                ret.addAll(allImpls);
            }
            
            if (impls2Remove != null && !impls2Remove.isEmpty()) {
                ret.removeAll(impls2Remove);
            }
            return ret;
        }
        
        private List<TypeMirror> findUsedGenericTypes(TypeElement javaClass) {
            List<TypeMirror> typeArgs = JavaRefactoringUtils.elementsToTypes(javaClass.getTypeParameters());
            if (typeArgs.isEmpty()) {
                return typeArgs;
            }

            Types typeUtils = workingCopy.getTypes();
            Set<TypeMirror> used = Collections.newSetFromMap(new IdentityHashMap<TypeMirror, Boolean>());

            // check super class
            TypeMirror superClass = javaClass.getSuperclass();
            RefactoringUtils.findUsedGenericTypes(typeUtils, typeArgs, used, superClass);

            MemberInfo[] members = refactoring.getMembers();
            for (int i = 0; i < members.length && !typeArgs.isEmpty(); i++) {
                if (members[i].getGroup() == MemberInfo.Group.METHOD) {
                // check methods
                    @SuppressWarnings("unchecked")
                    ElementHandle<ExecutableElement> handle = (ElementHandle<ExecutableElement>) members[i].getElementHandle();
                    ExecutableElement elm = handle.resolve(workingCopy);

                    RefactoringUtils.findUsedGenericTypes(typeUtils, typeArgs, used, elm.getReturnType());

                    for (Iterator<? extends VariableElement> paramIter = elm.getParameters().iterator(); paramIter.hasNext() && !typeArgs.isEmpty();) {
                        VariableElement param = paramIter.next();
                        RefactoringUtils.findUsedGenericTypes(typeUtils, typeArgs, used, param.asType());
                    }
                } else if (members[i].getGroup() == MemberInfo.Group.FIELD) {
                    if (members[i].getModifiers().contains(Modifier.STATIC)) {
                        // do not check since static fields cannot use type parameter of the enclosing class
                        continue;
                    }
                    @SuppressWarnings("unchecked")
                    ElementHandle<VariableElement> handle = (ElementHandle<VariableElement>) members[i].getElementHandle();
                    VariableElement elm = handle.resolve(workingCopy);
                    TypeMirror asType = elm.asType();
                    RefactoringUtils.findUsedGenericTypes(typeUtils, typeArgs, used, asType);
                } else if (members[i].getGroup() == MemberInfo.Group.IMPLEMENTS) {
                    // check implements
                    TypeMirrorHandle handle = (TypeMirrorHandle) members[i].getElementHandle();
                    TypeMirror implemetz = handle.resolve(workingCopy);
                    RefactoringUtils.findUsedGenericTypes(typeUtils, typeArgs, used, implemetz);
                }
                // do not check fields since static fields cannot use type parameter of the enclosing class
            }

            return RefactoringUtils.filterTypes(typeArgs, used);
        }
        
        // --- helper methods ----------------------------------
        
        /* in case there are constructors delegating to old superclass it is necessery to create delegates in new superclass */
        private void addConstructors(final TypeElement origClass) {
            final GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
            
            // cache of already resolved constructors
            final Set<Element> added = new HashSet<Element>();
            for (ExecutableElement constr : ElementFilter.constructorsIn(origClass.getEnclosedElements())) {
                if (workingCopy.getElementUtilities().isSynthetic(constr)) {
                    continue;
                }
                
                TreePath path = workingCopy.getTrees().getPath(constr);
                MethodTree mc = (MethodTree) (path != null? path.getLeaf(): null);
                if (mc != null) {
                    for (StatementTree stmt : mc.getBody().getStatements()) {
                        // search super(...); statement
                        if (stmt.getKind() == Tree.Kind.EXPRESSION_STATEMENT) {
                            ExpressionStatementTree estmt = (ExpressionStatementTree) stmt;
                            boolean isSyntheticSuper = workingCopy.getTreeUtilities().isSynthetic(workingCopy.getTrees().getPath(path.getCompilationUnit(), estmt));
                            ExpressionTree expr = estmt.getExpression();
                            TreePath expath = workingCopy.getTrees().getPath(path.getCompilationUnit(), expr);
                            Element el = workingCopy.getTrees().getElement(expath);
                            if (el != null && el.getKind() == ElementKind.CONSTRUCTOR && added.add(el)) {
                                ExecutableElement superclassConstr = (ExecutableElement) el;
                                MethodInvocationTree invk = (MethodInvocationTree) expr;
                                // create constructor block with super call
                                BlockTree block = isSyntheticSuper
                                        ? make.Block(Collections.<StatementTree>emptyList(), false)
                                        : make.Block(Collections.<StatementTree>singletonList(
                                            make.ExpressionStatement(
                                                make.MethodInvocation(
                                                    Collections.<ExpressionTree>emptyList(),
                                                    invk.getMethodSelect(),
                                                    params2Arguments(make, superclassConstr.getParameters())
                                                ))), false);
                                // create constructor
                                MethodTree newConstr = make.Method(superclassConstr, block);

                                newConstr = removeRuntimeExceptions(workingCopy, superclassConstr, make, newConstr);

                                newConstr = genUtils.importFQNs(newConstr);
                                members.add(newConstr);
                            }
                            
                        }
                        // take just first statement super(...)
                        break;
                    }
                }
            }
        }

        private static MethodTree removeRuntimeExceptions(final WorkingCopy javac, ExecutableElement superclassConstr, final TreeMaker make, MethodTree newConstr) {
            int i = 0;
            TypeMirror rte = javac.getElements().getTypeElement("java.lang.RuntimeException").asType(); //NOI18N
            ArrayList<Integer> rtes = new ArrayList<Integer>();
            for (TypeMirror throwz : superclassConstr.getThrownTypes()) {
                if (javac.getTypes().isSubtype(throwz, rte)) {
                    rtes.add(i);
                }
                i++;
            }
            for (int j = rtes.size()-1; j >= 0; j--) {
                newConstr = make.removeMethodThrows(newConstr, rtes.get(j));
            }
            return newConstr;
        }

        
        private static List<? extends ExpressionTree> params2Arguments(TreeMaker make, List<? extends VariableElement> params) {
            if (params.isEmpty()) {
                return Collections.<ExpressionTree>emptyList();
            }
            List<ExpressionTree> args = new ArrayList<ExpressionTree>(params.size());
            for (VariableElement param : params) {
                args.add(make.Identifier(param.getSimpleName()));
            }
            return args;
        }        
    }
}
