~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to junit/src/org/netbeans/modules/junit/JUnit4TestGenerator.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.modules.junit;
 
43
 
 
44
import com.sun.source.tree.AnnotationTree;
 
45
import com.sun.source.tree.BlockTree;
 
46
import com.sun.source.tree.ClassTree;
 
47
import com.sun.source.tree.ExpressionTree;
 
48
import com.sun.source.tree.MethodTree;
 
49
import com.sun.source.tree.ModifiersTree;
 
50
import com.sun.source.tree.StatementTree;
 
51
import com.sun.source.tree.Tree;
 
52
import com.sun.source.tree.TypeParameterTree;
 
53
import com.sun.source.tree.VariableTree;
 
54
import com.sun.source.util.TreePath;
 
55
import com.sun.source.util.Trees;
 
56
import java.util.ArrayList;
 
57
import java.util.Collections;
 
58
import java.util.EnumSet;
 
59
import java.util.Iterator;
 
60
import java.util.List;
 
61
import java.util.Map;
 
62
import java.util.Set;
 
63
import javax.lang.model.element.AnnotationMirror;
 
64
import javax.lang.model.element.AnnotationValue;
 
65
import javax.lang.model.element.Element;
 
66
import javax.lang.model.element.ElementKind;
 
67
import javax.lang.model.element.ExecutableElement;
 
68
import javax.lang.model.element.Modifier;
 
69
import javax.lang.model.element.Name;
 
70
import javax.lang.model.element.TypeElement;
 
71
import javax.lang.model.type.TypeKind;
 
72
import javax.lang.model.type.TypeMirror;
 
73
import javax.lang.model.util.Types;
 
74
import org.netbeans.api.java.source.ElementHandle;
 
75
import org.netbeans.api.java.source.TreeMaker;
 
76
import org.netbeans.api.java.source.WorkingCopy;
 
77
import static javax.lang.model.element.Modifier.PUBLIC;
 
78
import static javax.lang.model.element.Modifier.PROTECTED;
 
79
import static javax.lang.model.element.Modifier.PRIVATE;
 
80
import static javax.lang.model.element.Modifier.STATIC;
 
81
 
 
82
/**
 
83
 *
 
84
 * @author  Marian Petras
 
85
 */
 
86
final class JUnit4TestGenerator extends AbstractTestGenerator {
 
87
    
 
88
    /** */
 
89
    static final String ANN_BEFORE_CLASS = "org.junit.BeforeClass";     //NOI18N
 
90
    /** */
 
91
    static final String ANN_AFTER_CLASS = "org.junit.AfterClass";       //NOI18N
 
92
    /** */
 
93
    static final String ANN_BEFORE = "org.junit.Before";                //NOI18N
 
94
    /** */
 
95
    static final String ANN_AFTER = "org.junit.After";                  //NOI18N
 
96
    /** */
 
97
    static final String ANN_TEST = "org.junit.Test";                    //NOI18N
 
98
    /** */
 
99
    private static final String ANN_RUN_WITH = "org.junit.runner.RunWith";//NOI18N
 
100
    /** */
 
101
    private static final String ANN_SUITE = "org.junit.runners.Suite";  //NOI18N
 
102
    /** */
 
103
    private static final String ANN_SUITE_MEMBERS = "SuiteClasses";     //NOI18N
 
104
    /** */
 
105
    private static final String BEFORE_CLASS_METHOD_NAME = "setUpClass";//NOI18N
 
106
    /** */
 
107
    private static final String AFTER_CLASS_METHOD_NAME = "tearDownClass";//NOI18N
 
108
    /** */
 
109
    private static final String BEFORE_METHOD_NAME = "setUp";           //NOI18N
 
110
    /** */
 
111
    private static final String AFTER_METHOD_NAME = "tearDown";         //NOI18N
 
112
    
 
113
    /**
 
114
     */
 
115
    JUnit4TestGenerator(TestGeneratorSetup setup) {
 
116
        super(setup);
 
117
    }
 
118
    
 
119
    /**
 
120
     */
 
121
    JUnit4TestGenerator(TestGeneratorSetup setup,
 
122
                        List<ElementHandle<TypeElement>> srcTopClassHandles,
 
123
                        List<String>suiteMembers,
 
124
                        boolean isNewTestClass) {
 
125
        super(setup, srcTopClassHandles, suiteMembers, isNewTestClass);
 
126
    }
 
127
    
 
128
    
 
129
    /**
 
130
     */
 
131
    protected ClassTree composeNewTestClass(WorkingCopy workingCopy,
 
132
                                            String name,
 
133
                                            List<? extends Tree> members) {
 
134
        final TreeMaker maker = workingCopy.getTreeMaker();
 
135
        ModifiersTree modifiers = maker.Modifiers(
 
136
                                      Collections.<Modifier>singleton(PUBLIC));
 
137
        return maker.Class(
 
138
                    modifiers,                                 //modifiers
 
139
                    name,                                      //name
 
140
                    Collections.<TypeParameterTree>emptyList(),//type params
 
141
                    null,                                      //extends
 
142
                    Collections.<ExpressionTree>emptyList(),   //implements
 
143
                    members);                                  //members
 
144
    }
 
145
    
 
146
    /**
 
147
     */
 
148
    protected List<? extends Tree> generateInitMembers(WorkingCopy workingCopy) {
 
149
        if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
 
150
                && !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
 
151
            return Collections.<Tree>emptyList();
 
152
        }
 
153
 
 
154
        List<MethodTree> result = new ArrayList<MethodTree>(4);
 
155
        if (setup.isGenerateBeforeClass()) {
 
156
            result.add(
 
157
                    generateInitMethod(BEFORE_CLASS_METHOD_NAME, ANN_BEFORE_CLASS, true, workingCopy));
 
158
        }
 
159
        if (setup.isGenerateAfterClass()) {
 
160
            result.add(
 
161
                    generateInitMethod(AFTER_CLASS_METHOD_NAME, ANN_AFTER_CLASS, true, workingCopy));
 
162
        }
 
163
        if (setup.isGenerateBefore()) {
 
164
            result.add(
 
165
                    generateInitMethod(BEFORE_METHOD_NAME, ANN_BEFORE, false, workingCopy));
 
166
        }
 
167
        if (setup.isGenerateAfter()) {
 
168
            result.add(
 
169
                    generateInitMethod(AFTER_METHOD_NAME, ANN_AFTER, false, workingCopy));
 
170
        }
 
171
        return result;
 
172
    }
 
173
 
 
174
    /**
 
175
     */
 
176
    protected ClassTree generateMissingInitMembers(ClassTree tstClass,
 
177
                                                   TreePath tstClassTreePath,
 
178
                                                   WorkingCopy workingCopy) {
 
179
        if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
 
180
                && !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
 
181
            return tstClass;
 
182
        }
 
183
 
 
184
        ClassMap classMap = ClassMap.forClass(tstClass, tstClassTreePath,
 
185
                                              workingCopy.getTrees());
 
186
 
 
187
        if ((!setup.isGenerateBefore() || classMap.containsBefore())
 
188
                && (!setup.isGenerateAfter() || classMap.containsAfter())
 
189
                && (!setup.isGenerateBeforeClass() || classMap.containsBeforeClass())
 
190
                && (!setup.isGenerateAfterClass() || classMap.containsAfterClass())) {
 
191
            return tstClass;
 
192
        }
 
193
 
 
194
        final TreeMaker maker = workingCopy.getTreeMaker();
 
195
 
 
196
        List<? extends Tree> tstMembersOrig = tstClass.getMembers();
 
197
        List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 4);
 
198
        tstMembers.addAll(tstMembersOrig);
 
199
 
 
200
        generateMissingInitMembers(tstMembers, classMap, workingCopy);
 
201
 
 
202
        ClassTree newClass = maker.Class(
 
203
                tstClass.getModifiers(),
 
204
                tstClass.getSimpleName(),
 
205
                tstClass.getTypeParameters(),
 
206
                tstClass.getExtendsClause(),
 
207
                (List<? extends ExpressionTree>) tstClass.getImplementsClause(),
 
208
                tstMembers);
 
209
        return newClass;
 
210
    }
 
211
    
 
212
    /**
 
213
     */
 
214
    protected boolean generateMissingInitMembers(List<Tree> tstMembers,
 
215
                                               ClassMap clsMap,
 
216
                                               WorkingCopy workingCopy) {
 
217
        boolean modified = false;
 
218
        
 
219
        if (setup.isGenerateBeforeClass() && !clsMap.containsBeforeClass()) {
 
220
            int targetIndex;
 
221
            if (clsMap.containsAfterClass()) {
 
222
                targetIndex = clsMap.getAfterClassIndex();
 
223
            } else {
 
224
                int beforeIndex = clsMap.getBeforeIndex();
 
225
                int afterIndex = clsMap.getAfterIndex();
 
226
                if ((beforeIndex != -1) && (afterIndex != -1)) {
 
227
                    targetIndex = Math.min(beforeIndex, afterIndex);
 
228
                } else {
 
229
                    /*
 
230
                     * if (beforeIndex != -1)
 
231
                     *     targetIndex = beforeIndex;
 
232
                     * else if (afterIndex != -1)
 
233
                     *     targetIndex = afterIndex;
 
234
                     * else
 
235
                     *     targetIndex = -1;
 
236
                     */
 
237
                    targetIndex = Math.max(beforeIndex, afterIndex);
 
238
                }
 
239
            }
 
240
            addInitMethod(BEFORE_CLASS_METHOD_NAME,
 
241
                          ANN_BEFORE_CLASS,
 
242
                          true,
 
243
                          targetIndex,
 
244
                          tstMembers,
 
245
                          clsMap,
 
246
                          workingCopy);
 
247
            modified = true;
 
248
        }
 
249
        if (setup.isGenerateAfterClass() && !clsMap.containsAfterClass()) {
 
250
            int targetIndex;
 
251
            if (clsMap.containsBeforeClass()) {
 
252
                targetIndex = clsMap.getBeforeClassIndex() + 1;
 
253
            } else {
 
254
                int beforeIndex = clsMap.getBeforeIndex();
 
255
                int afterIndex = clsMap.getAfterIndex();
 
256
                if ((beforeIndex != -1) && (afterIndex != -1)) {
 
257
                    targetIndex = Math.min(beforeIndex, afterIndex);
 
258
                } else {
 
259
                    targetIndex = Math.max(beforeIndex, afterIndex);
 
260
                }
 
261
            }
 
262
            addInitMethod(AFTER_CLASS_METHOD_NAME,
 
263
                          ANN_AFTER_CLASS,
 
264
                          true,
 
265
                          targetIndex,
 
266
                          tstMembers,
 
267
                          clsMap,
 
268
                          workingCopy);
 
269
            modified = true;
 
270
        }
 
271
        if (setup.isGenerateBefore() && !clsMap.containsBefore()) {
 
272
            int targetIndex;
 
273
            if (clsMap.containsAfter()) {
 
274
                targetIndex = clsMap.getAfterIndex();
 
275
            } else {
 
276
                int beforeClassIndex = clsMap.getBeforeClassIndex();
 
277
                int afterClassIndex = clsMap.getAfterClassIndex();
 
278
                
 
279
                /*
 
280
                 * if ((beforeClassIndex != -1) && (afterClassIndex != -1))
 
281
                 *     targetIndex = Math.max(beforeClassIndex, afterClassIndex) + 1;
 
282
                 * else if (beforeClassIndex != -1)
 
283
                 *     targetIndex = beforeClassIndex + 1;
 
284
                 * else if (afterClassIndex != -1)
 
285
                 *     targetIndex = afterClassIndex + 1;
 
286
                 * else
 
287
                 *     targetIndex = -1
 
288
                 */
 
289
                targetIndex = Math.max(beforeClassIndex, afterClassIndex);
 
290
                if (targetIndex != -1) {
 
291
                    targetIndex++;
 
292
                }
 
293
            }
 
294
            addInitMethod(BEFORE_METHOD_NAME,
 
295
                          ANN_BEFORE,
 
296
                          false,
 
297
                          targetIndex,
 
298
                          tstMembers,
 
299
                          clsMap,
 
300
                          workingCopy);
 
301
            modified = true;
 
302
        }
 
303
        if (setup.isGenerateAfter() && !clsMap.containsAfter()) {
 
304
            int targetIndex;
 
305
            if (clsMap.containsBefore()) {
 
306
                targetIndex = clsMap.getBeforeIndex() + 1;
 
307
            } else {
 
308
                int beforeClassIndex = clsMap.getBeforeClassIndex();
 
309
                int afterClassIndex = clsMap.getAfterClassIndex();
 
310
                targetIndex = Math.max(beforeClassIndex, afterClassIndex);
 
311
                if (targetIndex != -1) {
 
312
                    targetIndex++;
 
313
                }
 
314
            }
 
315
            addInitMethod(AFTER_METHOD_NAME,
 
316
                          ANN_AFTER,
 
317
                          false,
 
318
                          targetIndex,
 
319
                          tstMembers,
 
320
                          clsMap,
 
321
                          workingCopy);
 
322
            modified = true;
 
323
        }
 
324
        
 
325
        return modified;
 
326
    }
 
327
 
 
328
    /**
 
329
     */
 
330
    private void addInitMethod(String methodName,
 
331
                               String annotationClassName,
 
332
                               boolean isStatic,
 
333
                               int targetIndex,
 
334
                               List<Tree> clsMembers,
 
335
                               ClassMap clsMap,
 
336
                               WorkingCopy workingCopy) {
 
337
        MethodTree initMethod = generateInitMethod(methodName,
 
338
                                                   annotationClassName,
 
339
                                                   isStatic,
 
340
                                                   workingCopy);
 
341
        
 
342
        if (targetIndex == -1) {
 
343
            targetIndex = getPlaceForFirstInitMethod(clsMap);
 
344
        }
 
345
        
 
346
        if (targetIndex != -1) {
 
347
            clsMembers.add(targetIndex, initMethod);
 
348
        } else {
 
349
            clsMembers.add(initMethod);
 
350
        }
 
351
        clsMap.addNoArgMethod(methodName, annotationClassName, targetIndex);
 
352
    }
 
353
 
 
354
    /**
 
355
     * Generates a set-up or a tear-down method.
 
356
     * The generated method will have no arguments, void return type
 
357
     * and a declaration that it may throw {@code java.lang.Exception}.
 
358
     * The method will have a declared protected member access.
 
359
     * The method contains call of the corresponding super method, i.e.
 
360
     * {@code super.setUp()} or {@code super.tearDown()}.
 
361
     *
 
362
     * @param  methodName  name of the method to be created
 
363
     * @return  created method
 
364
     * @see  http://junit.sourceforge.net/javadoc/junit/framework/TestCase.html
 
365
     *       methods {@code setUp()} and {@code tearDown()}
 
366
     */
 
367
    private MethodTree generateInitMethod(String methodName,
 
368
                                          String annotationClassName,
 
369
                                          boolean isStatic,
 
370
                                          WorkingCopy workingCopy) {
 
371
        Set<Modifier> methodModifiers
 
372
                = isStatic ? createModifierSet(PUBLIC, STATIC)
 
373
                           : Collections.<Modifier>singleton(PUBLIC);
 
374
        ModifiersTree modifiers = createModifiersTree(annotationClassName,
 
375
                                                      methodModifiers,
 
376
                                                      workingCopy);
 
377
        TreeMaker maker = workingCopy.getTreeMaker();
 
378
        BlockTree methodBody = maker.Block(
 
379
                Collections.<StatementTree>emptyList(),
 
380
                false);
 
381
        MethodTree method = maker.Method(
 
382
                modifiers,              // modifiers
 
383
                methodName,             // name
 
384
                maker.PrimitiveType(TypeKind.VOID),         // return type
 
385
                Collections.<TypeParameterTree>emptyList(), // type params
 
386
                Collections.<VariableTree>emptyList(),      // parameters
 
387
                Collections.<ExpressionTree>singletonList(
 
388
                        maker.Identifier("Exception")),     // throws...//NOI18N
 
389
                methodBody,
 
390
                null);                                      // default value
 
391
        return method;
 
392
    }
 
393
    
 
394
    /**
 
395
     */
 
396
    protected void generateMissingPostInitMethods(TreePath tstClassTreePath,
 
397
                                                  List<Tree> tstMembers,
 
398
                                                  ClassMap clsMap,
 
399
                                                  WorkingCopy workingCopy) {
 
400
        /* no post-init methods */
 
401
    }
 
402
    
 
403
    /**
 
404
     */
 
405
    @Override
 
406
    protected String createTestMethodName(String smName) {
 
407
        return smName;
 
408
    }
 
409
    
 
410
    /**
 
411
     */
 
412
    protected MethodTree composeNewTestMethod(String testMethodName,
 
413
                                              BlockTree testMethodBody,
 
414
                                              List<ExpressionTree> throwsList,
 
415
                                              WorkingCopy workingCopy) {
 
416
        TreeMaker maker = workingCopy.getTreeMaker();
 
417
        return maker.Method(
 
418
                createModifiersTree(ANN_TEST,
 
419
                                    createModifierSet(PUBLIC),
 
420
                                    workingCopy),
 
421
                testMethodName,
 
422
                maker.PrimitiveType(TypeKind.VOID),
 
423
                Collections.<TypeParameterTree>emptyList(),
 
424
                Collections.<VariableTree>emptyList(),
 
425
                throwsList,
 
426
                testMethodBody,
 
427
                null);          //default value - used by annotations
 
428
    }
 
429
    
 
430
    /**
 
431
     */
 
432
    protected ClassTree finishSuiteClass(ClassTree tstClass,
 
433
                                         TreePath tstClassTreePath,
 
434
                                         List<Tree> tstMembers,
 
435
                                         List<String> suiteMembers,
 
436
                                         boolean membersChanged,
 
437
                                         ClassMap classMap,
 
438
                                         WorkingCopy workingCopy) {
 
439
 
 
440
        ModifiersTree currModifiers = tstClass.getModifiers();
 
441
        ModifiersTree modifiers = fixSuiteClassModifiers(tstClass,
 
442
                                                         tstClassTreePath,
 
443
                                                         currModifiers,
 
444
                                                         suiteMembers,
 
445
                                                         workingCopy);
 
446
        if (!membersChanged) {
 
447
            if (modifiers != currModifiers) {
 
448
                workingCopy.rewrite(currModifiers, modifiers);
 
449
            }
 
450
            return tstClass;
 
451
        }
 
452
 
 
453
        return workingCopy.getTreeMaker().Class(
 
454
                modifiers,
 
455
                tstClass.getSimpleName(),
 
456
                tstClass.getTypeParameters(),
 
457
                tstClass.getExtendsClause(),
 
458
                (List<? extends ExpressionTree>) tstClass.getImplementsClause(),
 
459
                tstMembers);
 
460
    }
 
461
    
 
462
    /**
 
463
     * Keeps or modifies annotations and modifiers of the given suite class.
 
464
     * Modifiers are modified such that the class is public.
 
465
     * The list of annotations is modified such that it contains
 
466
     * the following annotations:
 
467
     * <pre><code>RunWith(Suite.class)
 
468
     * @SuiteRunner.Suite({...})</code></pre>
 
469
     * with members of the suite in place of the <code>{...}</code> list.
 
470
     * 
 
471
     * @param  tstClass  class whose modifiers and anntations are to be modified
 
472
     * @param  tstClassTreePath  tree path to the class from the compilation unit
 
473
     * @param  modifiers  current modifiers and annotations
 
474
     * @param  suiteMembers  list of class names that should be contained
 
475
     *                       in the test suite
 
476
     * @return  {@code ModifiersTree} object containing the modified set
 
477
     *          of class modifiers and annotations, or {@code null}
 
478
     *          if no modifications were necessary
 
479
     */
 
480
    private ModifiersTree fixSuiteClassModifiers(ClassTree tstClass,
 
481
                                                 TreePath tstClassTreePath,
 
482
                                                 ModifiersTree modifiers,
 
483
                                                 List<String> suiteMembers,
 
484
                                                 WorkingCopy workingCopy) {
 
485
        boolean flagsModified = false;
 
486
        
 
487
        Set<Modifier> currFlags = modifiers.getFlags();
 
488
        Set<Modifier> flags = EnumSet.copyOf(currFlags);
 
489
        flagsModified |= flags.remove(PRIVATE);
 
490
        flagsModified |= flags.remove(PROTECTED);
 
491
        flagsModified |= flags.add(PUBLIC);
 
492
        if (!flagsModified) {
 
493
            flags = currFlags;
 
494
        }
 
495
        
 
496
        
 
497
        boolean annotationListModified = false;
 
498
        
 
499
        List<? extends AnnotationTree> currAnnotations = modifiers.getAnnotations();
 
500
        List<? extends AnnotationTree> annotations;
 
501
        if (currAnnotations.isEmpty()) {
 
502
            List<AnnotationTree> newAnnotations = new ArrayList<AnnotationTree>(2);
 
503
            newAnnotations.add(createRunWithSuiteAnnotation(workingCopy));
 
504
            newAnnotations.add(createSuiteAnnotation(suiteMembers, workingCopy));
 
505
            annotations = newAnnotations;
 
506
            
 
507
            annotationListModified = true;
 
508
        } else {
 
509
            Trees trees = workingCopy.getTrees();
 
510
            Element classElement = trees.getElement(tstClassTreePath);
 
511
            List<? extends AnnotationMirror> annMirrors
 
512
                    = classElement.getAnnotationMirrors();
 
513
            assert annMirrors.size() == currAnnotations.size();
 
514
            
 
515
            
 
516
            int index = -1, runWithIndex = -1, suiteClassesIndex = -1;
 
517
            for (AnnotationMirror annMirror : annMirrors) {
 
518
                index++;
 
519
                Element annElement = annMirror.getAnnotationType().asElement();
 
520
                assert annElement instanceof TypeElement;
 
521
                TypeElement annTypeElem = (TypeElement) annElement;
 
522
                Name annFullName = annTypeElem.getQualifiedName();
 
523
                
 
524
                if ((runWithIndex == -1) && annFullName.contentEquals(ANN_RUN_WITH)) {
 
525
                    runWithIndex = index;
 
526
                } else if ((suiteClassesIndex == -1)
 
527
                        && annFullName.contentEquals(ANN_SUITE + '.' + ANN_SUITE_MEMBERS)) {
 
528
                    suiteClassesIndex = index;
 
529
                }
 
530
            }
 
531
            
 
532
            AnnotationTree runWithSuiteAnn;
 
533
            if ((runWithIndex == -1) || !checkRunWithSuiteAnnotation(
 
534
                                                annMirrors.get(runWithIndex),
 
535
                                                workingCopy)) {
 
536
                runWithSuiteAnn = createRunWithSuiteAnnotation(workingCopy);
 
537
            } else {
 
538
                runWithSuiteAnn = currAnnotations.get(runWithIndex);
 
539
            }
 
540
            
 
541
            AnnotationTree suiteClassesAnn;
 
542
            if ((suiteClassesIndex == -1) || !checkSuiteMembersAnnotation(
 
543
                                                      annMirrors.get(suiteClassesIndex),
 
544
                                                      suiteMembers,
 
545
                                                      workingCopy)) {
 
546
                suiteClassesAnn = createSuiteAnnotation(suiteMembers, workingCopy);
 
547
            } else {
 
548
                suiteClassesAnn = currAnnotations.get(suiteClassesIndex);
 
549
            }
 
550
            
 
551
            if ((runWithIndex != -1) && (suiteClassesIndex != -1)) {
 
552
                if (runWithSuiteAnn != currAnnotations.get(runWithIndex)) {
 
553
                    workingCopy.rewrite(
 
554
                            currAnnotations.get(runWithIndex),
 
555
                            runWithSuiteAnn);
 
556
                }
 
557
                if (suiteClassesAnn != currAnnotations.get(suiteClassesIndex)) {
 
558
                    workingCopy.rewrite(
 
559
                            currAnnotations.get(suiteClassesIndex),
 
560
                            suiteClassesAnn);
 
561
                }
 
562
                annotations = currAnnotations;
 
563
            } else {
 
564
                List<AnnotationTree> newAnnotations
 
565
                        = new ArrayList<AnnotationTree>(currAnnotations.size() + 2);
 
566
                if ((runWithIndex == -1) && (suiteClassesIndex == -1)) {
 
567
                    
 
568
                    /*
 
569
                     * put the @RunWith(...) and @Suite.SuiteClasses(...)
 
570
                     * annotations in front of other annotations
 
571
                     */
 
572
                    newAnnotations.add(runWithSuiteAnn);
 
573
                    newAnnotations.add(suiteClassesAnn);
 
574
                    if (!currAnnotations.isEmpty()) {
 
575
                        newAnnotations.addAll(currAnnotations);
 
576
                    }
 
577
                } else {
 
578
                    newAnnotations.addAll(currAnnotations);
 
579
                    if (runWithIndex == -1) {
 
580
                        assert suiteClassesIndex != 1;
 
581
                        
 
582
                        /*
 
583
                         * put the @RunWith(...) annotation
 
584
                         * just before the Suite.SuiteClasses(...) annotation
 
585
                         */
 
586
                        newAnnotations.add(suiteClassesIndex, runWithSuiteAnn);
 
587
                    } else {
 
588
                        assert runWithIndex != -1;
 
589
                        
 
590
                        /*
 
591
                         * put the @Suite.SuiteClasses(...) annotation
 
592
                         * just after the @RunWith(...) annotation
 
593
                         */
 
594
                        newAnnotations.add(runWithIndex + 1, suiteClassesAnn);
 
595
                    }
 
596
                }
 
597
                annotations = newAnnotations;
 
598
                
 
599
                annotationListModified = true;
 
600
            }
 
601
        }
 
602
        
 
603
        if (!flagsModified && !annotationListModified) {
 
604
            return modifiers;
 
605
        }
 
606
        
 
607
        return workingCopy.getTreeMaker().Modifiers(flags, annotations);
 
608
    }
 
609
    
 
610
    /**
 
611
     * Checks that the given annotation is of type
 
612
     * <code>{@value #ANN_RUN_WITH}</code> and contains argument
 
613
     * <code>{@value #ANN_SUITE}{@literal .class}</code>.
 
614
     * 
 
615
     * @param  annMirror  annotation to be checked
 
616
     * @return  {@code true} if the annotation meets the described criteria,
 
617
     *          {@code false} otherwise
 
618
     */
 
619
    private boolean checkRunWithSuiteAnnotation(AnnotationMirror annMirror,
 
620
                                                WorkingCopy workingCopy) {
 
621
        Map<? extends ExecutableElement,? extends AnnotationValue> annParams
 
622
                = annMirror.getElementValues();
 
623
        
 
624
        if (annParams.size() != 1) {
 
625
            return false;
 
626
        }
 
627
        
 
628
        AnnotationValue annValue = annParams.values().iterator().next();
 
629
        Name annValueClsName = getAnnotationValueClassName(annValue,
 
630
                                                           workingCopy.getTypes());
 
631
        return annValueClsName != null
 
632
               ? annValueClsName.contentEquals(ANN_SUITE)
 
633
               : false;
 
634
    }
 
635
    
 
636
    /**
 
637
     * Checks that the given annotation is of type
 
638
     * <code>{@value #ANN_SUITE}.{@value #ANN_SUITE_MEMBERS}</code>
 
639
     * and contains the given list of classes as (the only) argument,
 
640
     * in the same order.
 
641
     * 
 
642
     * @param  annMirror  annotation to be checked
 
643
     * @param  suiteMembers  list of fully qualified class names denoting
 
644
     *                       content of the test suite
 
645
     * @return  {@code true} if the annotation meets the described criteria,
 
646
     *          {@code false} otherwise
 
647
     */
 
648
    private boolean checkSuiteMembersAnnotation(AnnotationMirror annMirror,
 
649
                                                List<String> suiteMembers,
 
650
                                                WorkingCopy workingCopy) {
 
651
        Map<? extends ExecutableElement,? extends AnnotationValue> annParams
 
652
                = annMirror.getElementValues();
 
653
        
 
654
        if (annParams.size() != 1) {
 
655
            return false;
 
656
        }
 
657
        
 
658
        AnnotationValue annValue = annParams.values().iterator().next();
 
659
        Object value = annValue.getValue();
 
660
        if (value instanceof java.util.List) {
 
661
            List<? extends AnnotationValue> items
 
662
                    = (List<? extends AnnotationValue>) value;
 
663
            
 
664
            if (items.size() != suiteMembers.size()) {
 
665
                return false;
 
666
            }
 
667
            
 
668
            Types types = workingCopy.getTypes();
 
669
            Iterator<String> suiteMembersIt = suiteMembers.iterator();
 
670
            for (AnnotationValue item : items) {
 
671
                Name suiteMemberName = getAnnotationValueClassName(item, types);
 
672
                if (suiteMemberName == null) {
 
673
                    return false;
 
674
                }
 
675
                if (!suiteMemberName.contentEquals(suiteMembersIt.next())) {
 
676
                    return false;
 
677
                }
 
678
            }
 
679
            return true;
 
680
        }
 
681
        
 
682
        return false;
 
683
    }
 
684
    
 
685
    /**
 
686
     * Returns fully qualified class name of a class given to an annotation
 
687
     * as (the only) argument.
 
688
     * 
 
689
     * @param  annValue  annotation value
 
690
     * @return  fully qualified name of a class represented by the given
 
691
     *          annotation value, or {@code null} if the annotation value
 
692
     *          does not represent a class
 
693
     */
 
694
    private Name getAnnotationValueClassName(AnnotationValue annValue,
 
695
                                             Types types) {
 
696
        Object value = annValue.getValue();
 
697
        if (value instanceof TypeMirror) {
 
698
            TypeMirror typeMirror = (TypeMirror) value;
 
699
            Element typeElement = types.asElement(typeMirror);
 
700
            if (typeElement.getKind() == ElementKind.CLASS) {
 
701
                return ((TypeElement) typeElement).getQualifiedName();
 
702
            }
 
703
        }
 
704
        return null;
 
705
    }
 
706
    
 
707
    /**
 
708
     * Creates annotation <code>@org.junit.runner.RunWith</code>.
 
709
     * 
 
710
     * @return  created annotation
 
711
     */
 
712
    private AnnotationTree createRunWithSuiteAnnotation(
 
713
                                                WorkingCopy workingCopy) {
 
714
        TreeMaker maker = workingCopy.getTreeMaker();
 
715
        
 
716
        /* @RunWith(Suite.class) */
 
717
        return maker.Annotation(
 
718
                getClassIdentifierTree(ANN_RUN_WITH, workingCopy),
 
719
                Collections.<ExpressionTree>singletonList(
 
720
                        maker.MemberSelect(
 
721
                                getClassIdentifierTree(ANN_SUITE, workingCopy),
 
722
                                "class")));                             //NOI18N
 
723
    }
 
724
    
 
725
    /**
 
726
     * Creates annotation
 
727
     * <code>@org.junit.runners.Suite.SuiteClasses({...})</code>.
 
728
     * 
 
729
     * @param  suiteMembers  fully qualified names of classes to be included
 
730
     *                       in the test suite
 
731
     * @param  created annotation
 
732
     */
 
733
    private AnnotationTree createSuiteAnnotation(List<String> suiteMembers,
 
734
                                                 WorkingCopy workingCopy) {
 
735
        final TreeMaker maker = workingCopy.getTreeMaker();
 
736
        
 
737
        List<ExpressionTree> suiteMemberExpressions
 
738
                = new ArrayList<ExpressionTree>(suiteMembers.size());
 
739
        for (String suiteMember : suiteMembers) {
 
740
            suiteMemberExpressions.add(
 
741
                    maker.MemberSelect(
 
742
                            getClassIdentifierTree(suiteMember, workingCopy),
 
743
                            "class"));                                  //NOI18N
 
744
        }
 
745
        
 
746
        /* @Suite.SuiteClasses({TestA.class, TestB.class, ...}) */
 
747
        return maker.Annotation(
 
748
                maker.MemberSelect(
 
749
                        getClassIdentifierTree(ANN_SUITE, workingCopy),
 
750
                        ANN_SUITE_MEMBERS),
 
751
                Collections.singletonList(
 
752
                        maker.NewArray(
 
753
                                null,  //do not generate "new Class[]"
 
754
                                Collections.<ExpressionTree>emptyList(),
 
755
                                suiteMemberExpressions)));
 
756
    }
 
757
 
 
758
}