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

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
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.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.WorkingCopy;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.PROTECTED;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;

/**
 *
 * @author  Marian Petras
 */
final class TestGenerator extends AbstractTestGenerator {
    
    /** */
    static final String ANN_BEFORE_CLASS = "org.testng.annotations.BeforeClass";     //NOI18N
    /** */
    static final String ANN_AFTER_CLASS = "org.testng.annotations.AfterClass";       //NOI18N
    /** */
    static final String ANN_BEFORE = "org.testng.annotations.BeforeMethod";                //NOI18N
    /** */
    static final String ANN_AFTER = "org.testng.annotations.AfterMethod";                  //NOI18N
    /** */
    static final String ANN_TEST = "org.testng.annotations.Test";                    //NOI18N
    /** */
//    private static final String ANN_RUN_WITH = "org.junit.runner.RunWith";//NOI18N
//    /** */
//    private static final String ANN_SUITE = "org.junit.runners.Suite";  //NOI18N
    /** */
    private static final String ANN_SUITE_MEMBERS = "SuiteClasses";     //NOI18N
    /** */
    private static final String BEFORE_CLASS_METHOD_NAME = "setUpClass";//NOI18N
    /** */
    private static final String AFTER_CLASS_METHOD_NAME = "tearDownClass";//NOI18N
    /** */
    private static final String BEFORE_METHOD_NAME = "setUpMethod";           //NOI18N
    /** */
    private static final String AFTER_METHOD_NAME = "tearDownMethod";         //NOI18N
    
    /**
     */
    TestGenerator(TestGeneratorSetup setup) {
        super(setup);
    }
    
    /**
     */
    TestGenerator(TestGeneratorSetup setup,
                        List<ElementHandle<TypeElement>> srcTopClassHandles,
                        List<String>suiteMembers,
                        boolean isNewTestClass) {
        super(setup, srcTopClassHandles, suiteMembers, isNewTestClass);
    }
    
    
    /**
     */
    @Override
    protected ClassTree composeNewTestClass(WorkingCopy workingCopy,
                                            String name,
                                            List<? extends Tree> members) {
        final TreeMaker maker = workingCopy.getTreeMaker();
        ModifiersTree modifiers = maker.Modifiers(
                                      Collections.<Modifier>singleton(PUBLIC));
        return maker.Class(
                    modifiers,                                 //modifiers
                    name,                                      //name
                    Collections.<TypeParameterTree>emptyList(),//type params
                    null,                                      //extends
                    Collections.<ExpressionTree>emptyList(),   //implements
                    members);                                  //members
    }
    
    /**
     */
    @Override
    protected List<? extends Tree> generateInitMembers(WorkingCopy workingCopy) {
        if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
                && !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
            return Collections.<Tree>emptyList();
        }

        List<MethodTree> result = new ArrayList<MethodTree>(4);
        if (setup.isGenerateBeforeClass()) {
            result.add(
                    generateInitMethod(BEFORE_CLASS_METHOD_NAME, ANN_BEFORE_CLASS, true, workingCopy));
        }
        if (setup.isGenerateAfterClass()) {
            result.add(
                    generateInitMethod(AFTER_CLASS_METHOD_NAME, ANN_AFTER_CLASS, true, workingCopy));
        }
        if (setup.isGenerateBefore()) {
            result.add(
                    generateInitMethod(BEFORE_METHOD_NAME, ANN_BEFORE, false, workingCopy));
        }
        if (setup.isGenerateAfter()) {
            result.add(
                    generateInitMethod(AFTER_METHOD_NAME, ANN_AFTER, false, workingCopy));
        }
        return result;
    }

    /**
     */
    @Override
    protected ClassTree generateMissingInitMembers(ClassTree tstClass,
                                                   TreePath tstClassTreePath,
                                                   WorkingCopy workingCopy) {
        if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
                && !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
            return tstClass;
        }

        ClassMap classMap = ClassMap.forClass(tstClass, tstClassTreePath,
                                              workingCopy.getTrees());

        if ((!setup.isGenerateBefore() || classMap.containsBefore())
                && (!setup.isGenerateAfter() || classMap.containsAfter())
                && (!setup.isGenerateBeforeClass() || classMap.containsBeforeClass())
                && (!setup.isGenerateAfterClass() || classMap.containsAfterClass())) {
            return tstClass;
        }

        final TreeMaker maker = workingCopy.getTreeMaker();

        List<? extends Tree> tstMembersOrig = tstClass.getMembers();
        List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 4);
        tstMembers.addAll(tstMembersOrig);

        generateMissingInitMembers(tstMembers, classMap, workingCopy);

        ClassTree newClass = maker.Class(
                tstClass.getModifiers(),
                tstClass.getSimpleName(),
                tstClass.getTypeParameters(),
                tstClass.getExtendsClause(),
                (List<? extends ExpressionTree>) tstClass.getImplementsClause(),
                tstMembers);
        return newClass;
    }
    
    /**
     */
    @Override
    protected boolean generateMissingInitMembers(List<Tree> tstMembers,
                                               ClassMap clsMap,
                                               WorkingCopy workingCopy) {
        boolean modified = false;
        
        if (setup.isGenerateBeforeClass() && !clsMap.containsBeforeClass()) {
            int targetIndex;
            if (clsMap.containsAfterClass()) {
                targetIndex = clsMap.getAfterClassIndex();
            } else {
                int beforeIndex = clsMap.getBeforeIndex();
                int afterIndex = clsMap.getAfterIndex();
                if ((beforeIndex != -1) && (afterIndex != -1)) {
                    targetIndex = Math.min(beforeIndex, afterIndex);
                } else {
                    /*
                     * if (beforeIndex != -1)
                     *     targetIndex = beforeIndex;
                     * else if (afterIndex != -1)
                     *     targetIndex = afterIndex;
                     * else
                     *     targetIndex = -1;
                     */
                    targetIndex = Math.max(beforeIndex, afterIndex);
                }
            }
            addInitMethod(BEFORE_CLASS_METHOD_NAME,
                          ANN_BEFORE_CLASS,
                          true,
                          targetIndex,
                          tstMembers,
                          clsMap,
                          workingCopy);
            modified = true;
        }
        if (setup.isGenerateAfterClass() && !clsMap.containsAfterClass()) {
            int targetIndex;
            if (clsMap.containsBeforeClass()) {
                targetIndex = clsMap.getBeforeClassIndex() + 1;
            } else {
                int beforeIndex = clsMap.getBeforeIndex();
                int afterIndex = clsMap.getAfterIndex();
                if ((beforeIndex != -1) && (afterIndex != -1)) {
                    targetIndex = Math.min(beforeIndex, afterIndex);
                } else {
                    targetIndex = Math.max(beforeIndex, afterIndex);
                }
            }
            addInitMethod(AFTER_CLASS_METHOD_NAME,
                          ANN_AFTER_CLASS,
                          true,
                          targetIndex,
                          tstMembers,
                          clsMap,
                          workingCopy);
            modified = true;
        }
        if (setup.isGenerateBefore() && !clsMap.containsBefore()) {
            int targetIndex;
            if (clsMap.containsAfter()) {
                targetIndex = clsMap.getAfterIndex();
            } else {
                int beforeClassIndex = clsMap.getBeforeClassIndex();
                int afterClassIndex = clsMap.getAfterClassIndex();
                
                /*
                 * if ((beforeClassIndex != -1) && (afterClassIndex != -1))
                 *     targetIndex = Math.max(beforeClassIndex, afterClassIndex) + 1;
                 * else if (beforeClassIndex != -1)
                 *     targetIndex = beforeClassIndex + 1;
                 * else if (afterClassIndex != -1)
                 *     targetIndex = afterClassIndex + 1;
                 * else
                 *     targetIndex = -1
                 */
                targetIndex = Math.max(beforeClassIndex, afterClassIndex);
                if (targetIndex != -1) {
                    targetIndex++;
                }
            }
            addInitMethod(BEFORE_METHOD_NAME,
                          ANN_BEFORE,
                          false,
                          targetIndex,
                          tstMembers,
                          clsMap,
                          workingCopy);
            modified = true;
        }
        if (setup.isGenerateAfter() && !clsMap.containsAfter()) {
            int targetIndex;
            if (clsMap.containsBefore()) {
                targetIndex = clsMap.getBeforeIndex() + 1;
            } else {
                int beforeClassIndex = clsMap.getBeforeClassIndex();
                int afterClassIndex = clsMap.getAfterClassIndex();
                targetIndex = Math.max(beforeClassIndex, afterClassIndex);
                if (targetIndex != -1) {
                    targetIndex++;
                }
            }
            addInitMethod(AFTER_METHOD_NAME,
                          ANN_AFTER,
                          false,
                          targetIndex,
                          tstMembers,
                          clsMap,
                          workingCopy);
            modified = true;
        }
        
        return modified;
    }

    /**
     */
    private void addInitMethod(String methodName,
                               String annotationClassName,
                               boolean isStatic,
                               int targetIndex,
                               List<Tree> clsMembers,
                               ClassMap clsMap,
                               WorkingCopy workingCopy) {
        MethodTree initMethod = generateInitMethod(methodName,
                                                   annotationClassName,
                                                   isStatic,
                                                   workingCopy);
        
        if (targetIndex == -1) {
            targetIndex = getPlaceForFirstInitMethod(clsMap);
        }
        
        if (targetIndex != -1) {
            clsMembers.add(targetIndex, initMethod);
        } else {
            clsMembers.add(initMethod);
        }
        clsMap.addNoArgMethod(methodName, annotationClassName, targetIndex);
    }

    /**
     * Generates a set-up or a tear-down method.
     * The generated method will have no arguments, void return type
     * and a declaration that it may throw {@code java.lang.Exception}.
     * The method will have a declared protected member access.
     * The method contains call of the corresponding super method, i.e.
     * {@code super.setUp()} or {@code super.tearDown()}.
     *
     * @param  methodName  name of the method to be created
     * @return  created method
     */
    private MethodTree generateInitMethod(String methodName,
                                          String annotationClassName,
                                          boolean isStatic,
                                          WorkingCopy workingCopy) {
        Set<Modifier> methodModifiers
                = isStatic ? createModifierSet(PUBLIC, STATIC)
                           : Collections.<Modifier>singleton(PUBLIC);
        ModifiersTree modifiers = createModifiersTree(annotationClassName,
                                                      methodModifiers,
                                                      workingCopy);
        TreeMaker maker = workingCopy.getTreeMaker();
        BlockTree methodBody = maker.Block(
                Collections.<StatementTree>emptyList(),
                false);
        MethodTree method = maker.Method(
                modifiers,              // modifiers
                methodName,             // name
                maker.PrimitiveType(TypeKind.VOID),         // return type
                Collections.<TypeParameterTree>emptyList(), // type params
                Collections.<VariableTree>emptyList(),      // parameters
                Collections.<ExpressionTree>singletonList(
                        maker.Identifier("Exception")),     // throws...//NOI18N
                methodBody,
                null);                                      // default value
        return method;
    }
    
    /**
     */
    @Override
    protected void generateMissingPostInitMethods(TreePath tstClassTreePath,
                                                  List<Tree> tstMembers,
                                                  ClassMap clsMap,
                                                  WorkingCopy workingCopy) {
        /* no post-init methods */
    }
    
    /**
     */
    @Override
    protected MethodTree composeNewTestMethod(String testMethodName,
                                              BlockTree testMethodBody,
                                              List<ExpressionTree> throwsList,
                                              WorkingCopy workingCopy) {
        TreeMaker maker = workingCopy.getTreeMaker();
        return maker.Method(
                createModifiersTree(ANN_TEST,
                                    createModifierSet(PUBLIC),
                                    workingCopy),
                testMethodName,
                maker.PrimitiveType(TypeKind.VOID),
                Collections.<TypeParameterTree>emptyList(),
                Collections.<VariableTree>emptyList(),
                throwsList,
                testMethodBody,
                null);          //default value - used by annotations
    }
    
    /**
     */
//    @Override
//    protected ClassTree finishSuiteClass(ClassTree tstClass,
//                                         TreePath tstClassTreePath,
//                                         List<Tree> tstMembers,
//                                         List<String> suiteMembers,
//                                         boolean membersChanged,
//                                         ClassMap classMap,
//                                         WorkingCopy workingCopy) {
//
//        ModifiersTree currModifiers = tstClass.getModifiers();
//        ModifiersTree modifiers = fixSuiteClassModifiers(tstClass,
//                                                         tstClassTreePath,
//                                                         currModifiers,
//                                                         suiteMembers,
//                                                         workingCopy);
//        if (!membersChanged) {
//            if (modifiers != currModifiers) {
//                workingCopy.rewrite(currModifiers, modifiers);
//            }
//            return tstClass;
//        }
//
//        return workingCopy.getTreeMaker().Class(
//                modifiers,
//                tstClass.getSimpleName(),
//                tstClass.getTypeParameters(),
//                tstClass.getExtendsClause(),
//                (List<? extends ExpressionTree>) tstClass.getImplementsClause(),
//                tstMembers);
//    }
    
//    /**
//     * Keeps or modifies annotations and modifiers of the given suite class.
//     * Modifiers are modified such that the class is public.
//     * The list of annotations is modified such that it contains
//     * the following annotations:
//     * <pre><code>RunWith(Suite.class)
//     * @SuiteRunner.Suite({...})</code></pre>
//     * with members of the suite in place of the <code>{...}</code> list.
//     * 
//     * @param  tstClass  class whose modifiers and anntations are to be modified
//     * @param  tstClassTreePath  tree path to the class from the compilation unit
//     * @param  modifiers  current modifiers and annotations
//     * @param  suiteMembers  list of class names that should be contained
//     *                       in the test suite
//     * @return  {@code ModifiersTree} object containing the modified set
//     *          of class modifiers and annotations, or {@code null}
//     *          if no modifications were necessary
//     */
//    private ModifiersTree fixSuiteClassModifiers(ClassTree tstClass,
//                                                 TreePath tstClassTreePath,
//                                                 ModifiersTree modifiers,
//                                                 List<String> suiteMembers,
//                                                 WorkingCopy workingCopy) {
//        boolean flagsModified = false;
//        
//        Set<Modifier> currFlags = modifiers.getFlags();
//        Set<Modifier> flags = EnumSet.copyOf(currFlags);
//        flagsModified |= flags.remove(PRIVATE);
//        flagsModified |= flags.remove(PROTECTED);
//        flagsModified |= flags.add(PUBLIC);
//        if (!flagsModified) {
//            flags = currFlags;
//        }
//        
//        
//        boolean annotationListModified = false;
//        
//        List<? extends AnnotationTree> currAnnotations = modifiers.getAnnotations();
//        List<? extends AnnotationTree> annotations;
//        if (currAnnotations.isEmpty()) {
//            List<AnnotationTree> newAnnotations = new ArrayList<AnnotationTree>(2);
//            newAnnotations.add(createRunWithSuiteAnnotation(workingCopy));
//            newAnnotations.add(createSuiteAnnotation(suiteMembers, workingCopy));
//            annotations = newAnnotations;
//            
//            annotationListModified = true;
//        } else {
//            Trees trees = workingCopy.getTrees();
//            Element classElement = trees.getElement(tstClassTreePath);
//            List<? extends AnnotationMirror> annMirrors
//                    = classElement.getAnnotationMirrors();
//            assert annMirrors.size() == currAnnotations.size();
//            
//            
//            int index = -1, runWithIndex = -1, suiteClassesIndex = -1;
//            for (AnnotationMirror annMirror : annMirrors) {
//                index++;
//                Element annElement = annMirror.getAnnotationType().asElement();
//                assert annElement instanceof TypeElement;
//                TypeElement annTypeElem = (TypeElement) annElement;
//                Name annFullName = annTypeElem.getQualifiedName();
//                
//                if ((runWithIndex == -1) && annFullName.contentEquals(ANN_RUN_WITH)) {
//                    runWithIndex = index;
//                } else if ((suiteClassesIndex == -1)
//                        && annFullName.contentEquals(ANN_SUITE + '.' + ANN_SUITE_MEMBERS)) {
//                    suiteClassesIndex = index;
//                }
//            }
//            
//            AnnotationTree runWithSuiteAnn;
//            if ((runWithIndex == -1) || !checkRunWithSuiteAnnotation(
//                                                annMirrors.get(runWithIndex),
//                                                workingCopy)) {
//                runWithSuiteAnn = createRunWithSuiteAnnotation(workingCopy);
//            } else {
//                runWithSuiteAnn = currAnnotations.get(runWithIndex);
//            }
//            
//            AnnotationTree suiteClassesAnn;
//            if ((suiteClassesIndex == -1) || !checkSuiteMembersAnnotation(
//                                                      annMirrors.get(suiteClassesIndex),
//                                                      suiteMembers,
//                                                      workingCopy)) {
//                suiteClassesAnn = createSuiteAnnotation(suiteMembers, workingCopy);
//            } else {
//                suiteClassesAnn = currAnnotations.get(suiteClassesIndex);
//            }
//            
//            if ((runWithIndex != -1) && (suiteClassesIndex != -1)) {
//                if (runWithSuiteAnn != currAnnotations.get(runWithIndex)) {
//                    workingCopy.rewrite(
//                            currAnnotations.get(runWithIndex),
//                            runWithSuiteAnn);
//                }
//                if (suiteClassesAnn != currAnnotations.get(suiteClassesIndex)) {
//                    workingCopy.rewrite(
//                            currAnnotations.get(suiteClassesIndex),
//                            suiteClassesAnn);
//                }
//                annotations = currAnnotations;
//            } else {
//                List<AnnotationTree> newAnnotations
//                        = new ArrayList<AnnotationTree>(currAnnotations.size() + 2);
//                if ((runWithIndex == -1) && (suiteClassesIndex == -1)) {
//                    
//                    /*
//                     * put the @RunWith(...) and @Suite.SuiteClasses(...)
//                     * annotations in front of other annotations
//                     */
//                    newAnnotations.add(runWithSuiteAnn);
//                    newAnnotations.add(suiteClassesAnn);
//                    if (!currAnnotations.isEmpty()) {
//                        newAnnotations.addAll(currAnnotations);
//                    }
//                } else {
//                    newAnnotations.addAll(currAnnotations);
//                    if (runWithIndex == -1) {
//                        assert suiteClassesIndex != 1;
//                        
//                        /*
//                         * put the @RunWith(...) annotation
//                         * just before the Suite.SuiteClasses(...) annotation
//                         */
//                        newAnnotations.add(suiteClassesIndex, runWithSuiteAnn);
//                    } else {
//                        assert runWithIndex != -1;
//                        
//                        /*
//                         * put the @Suite.SuiteClasses(...) annotation
//                         * just after the @RunWith(...) annotation
//                         */
//                        newAnnotations.add(runWithIndex + 1, suiteClassesAnn);
//                    }
//                }
//                annotations = newAnnotations;
//                
//                annotationListModified = true;
//            }
//        }
//        
//        if (!flagsModified && !annotationListModified) {
//            return modifiers;
//        }
//        
//        return workingCopy.getTreeMaker().Modifiers(flags, annotations);
//    }
    
//    /**
//     * Checks that the given annotation is of type
//     * <code>{@value #ANN_RUN_WITH}</code> and contains argument
//     * <code>{@value #ANN_SUITE}{@literal .class}</code>.
//     * 
//     * @param  annMirror  annotation to be checked
//     * @return  {@code true} if the annotation meets the described criteria,
//     *          {@code false} otherwise
//     */
//    private boolean checkRunWithSuiteAnnotation(AnnotationMirror annMirror,
//                                                WorkingCopy workingCopy) {
//        Map<? extends ExecutableElement,? extends AnnotationValue> annParams
//                = annMirror.getElementValues();
//        
//        if (annParams.size() != 1) {
//            return false;
//        }
//        
//        AnnotationValue annValue = annParams.values().iterator().next();
//        Name annValueClsName = getAnnotationValueClassName(annValue,
//                                                           workingCopy.getTypes());
//        return annValueClsName != null
//               ? annValueClsName.contentEquals(ANN_SUITE)
//               : false;
//    }
    
    /**
     * Checks that the given annotation is of type
     * <code>{@value #ANN_SUITE}.{@value #ANN_SUITE_MEMBERS}</code>
     * and contains the given list of classes as (the only) argument,
     * in the same order.
     * 
     * @param  annMirror  annotation to be checked
     * @param  suiteMembers  list of fully qualified class names denoting
     *                       content of the test suite
     * @return  {@code true} if the annotation meets the described criteria,
     *          {@code false} otherwise
     */
    private boolean checkSuiteMembersAnnotation(AnnotationMirror annMirror,
                                                List<String> suiteMembers,
                                                WorkingCopy workingCopy) {
        Map<? extends ExecutableElement,? extends AnnotationValue> annParams
                = annMirror.getElementValues();
        
        if (annParams.size() != 1) {
            return false;
        }
        
        AnnotationValue annValue = annParams.values().iterator().next();
        Object value = annValue.getValue();
        if (value instanceof java.util.List) {
            List<? extends AnnotationValue> items
                    = (List<? extends AnnotationValue>) value;
            
            if (items.size() != suiteMembers.size()) {
                return false;
            }
            
            Types types = workingCopy.getTypes();
            Iterator<String> suiteMembersIt = suiteMembers.iterator();
            for (AnnotationValue item : items) {
                Name suiteMemberName = getAnnotationValueClassName(item, types);
                if (suiteMemberName == null) {
                    return false;
                }
                if (!suiteMemberName.contentEquals(suiteMembersIt.next())) {
                    return false;
                }
            }
            return true;
        }
        
        return false;
    }
    
    /**
     * Returns fully qualified class name of a class given to an annotation
     * as (the only) argument.
     * 
     * @param  annValue  annotation value
     * @return  fully qualified name of a class represented by the given
     *          annotation value, or {@code null} if the annotation value
     *          does not represent a class
     */
    private Name getAnnotationValueClassName(AnnotationValue annValue,
                                             Types types) {
        Object value = annValue.getValue();
        if (value instanceof TypeMirror) {
            TypeMirror typeMirror = (TypeMirror) value;
            Element typeElement = types.asElement(typeMirror);
            if (typeElement.getKind() == ElementKind.CLASS) {
                return ((TypeElement) typeElement).getQualifiedName();
            }
        }
        return null;
    }
    
//    /**
//     * Creates annotation <code>@org.junit.runner.RunWith</code>.
//     * 
//     * @return  created annotation
//     */
//    private AnnotationTree createRunWithSuiteAnnotation(
//                                                WorkingCopy workingCopy) {
//        TreeMaker maker = workingCopy.getTreeMaker();
//        
//        /* @RunWith(Suite.class) */
//        return maker.Annotation(
//                getClassIdentifierTree(ANN_RUN_WITH, workingCopy),
//                Collections.<ExpressionTree>singletonList(
//                        maker.MemberSelect(
//                                getClassIdentifierTree(ANN_SUITE, workingCopy),
//                                "class")));                             //NOI18N
//    }
//    
//    /**
//     * Creates annotation
//     * <code>@org.junit.runners.Suite.SuiteClasses({...})</code>.
//     * 
//     * @param  suiteMembers  fully qualified names of classes to be included
//     *                       in the test suite
//     * @param  created annotation
//     */
//    private AnnotationTree createSuiteAnnotation(List<String> suiteMembers,
//                                                 WorkingCopy workingCopy) {
//        final TreeMaker maker = workingCopy.getTreeMaker();
//        
//        List<ExpressionTree> suiteMemberExpressions
//                = new ArrayList<ExpressionTree>(suiteMembers.size());
//        for (String suiteMember : suiteMembers) {
//            suiteMemberExpressions.add(
//                    maker.MemberSelect(
//                            getClassIdentifierTree(suiteMember, workingCopy),
//                            "class"));                                  //NOI18N
//        }
//        
//        /* @Suite.SuiteClasses({TestA.class, TestB.class, ...}) */
//        return maker.Annotation(
//                maker.MemberSelect(
//                        getClassIdentifierTree(ANN_SUITE, workingCopy),
//                        ANN_SUITE_MEMBERS),
//                Collections.singletonList(
//                        maker.NewArray(
//                                null,  //do not generate "new Class[]"
//                                Collections.<ExpressionTree>emptyList(),
//                                suiteMemberExpressions)));
//    }

}
