001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.junit;
043:
044: import com.sun.source.tree.ClassTree;
045: import com.sun.source.tree.MethodTree;
046: import com.sun.source.tree.Tree;
047: import com.sun.source.util.TreePath;
048: import com.sun.source.util.Trees;
049: import java.util.ArrayList;
050: import java.util.Collections;
051: import java.util.List;
052: import javax.lang.model.element.AnnotationMirror;
053: import javax.lang.model.element.Element;
054: import javax.lang.model.element.ElementKind;
055: import javax.lang.model.element.Name;
056: import javax.lang.model.element.TypeElement;
057:
058: /**
059: * Data structure that holds overview of class' members and their positions
060: * within the class.
061: * <p>To get an instance for a class, use static method
062: * {@link #forClass}.</p>
063: *
064: * @author Marian Petras
065: */
066: final class ClassMap {
067:
068: private static final int SETUP_POS_INDEX = 0;
069: private static final int TEARDOWN_POS_INDEX = 1;
070: private static final int FIRST_METHOD_POS_INDEX = 2;
071: private static final int LAST_INIT_POS_INDEX = 3;
072: private static final int FIRST_NESTED_POS_INDEX = 4;
073: private static final int BEFORE_POS_INDEX = 5;
074: private static final int AFTER_POS_INDEX = 6;
075: private static final int BEFORE_CLASS_POS_INDEX = 7;
076: private static final int AFTER_CLASS_POS_INDEX = 8;
077:
078: private static final String JUNIT4_PKG_PREFIX = "org.junit."; //NOI18N
079:
080: /**
081: */
082: private final List<String> signatures;
083: /** */
084: private final int[] positions;
085:
086: /** Creates a new instance of ClassMap */
087: private ClassMap(List<String> signatures) {
088: this .signatures = signatures;
089: }
090:
091: {
092: positions = new int[9];
093: for (int i = 0; i < positions.length; i++) {
094: positions[i] = -1;
095: }
096: }
097:
098: /**
099: *
100: * @exception java.lang.IllegalArgumentException
101: * if any of the arguments is {@code null}
102: * or if the specified {@code ClassTree} is not a leaf
103: * of the specified {@code TreePath}
104: */
105: static ClassMap forClass(ClassTree cls, TreePath clsTreePath,
106: Trees trees) {
107: if (cls == null) {
108: throw new IllegalArgumentException("ClassTree: null"); //NOI18N
109: }
110: if (clsTreePath == null) {
111: throw new IllegalArgumentException("TreePath: null"); //NOI18N
112: }
113: if (clsTreePath.getLeaf() != cls) {
114: throw new IllegalArgumentException(
115: "given ClassTree is not leaf of the given TreePath"); //NOI18N
116: }
117:
118: List<? extends Tree> members = cls.getMembers();
119: if (members.isEmpty()) {
120: return new ClassMap(new ArrayList<String>());
121: }
122:
123: List<String> entries = new ArrayList<String>(members.size());
124: ClassMap map = new ClassMap(entries);
125:
126: int index = 0;
127: for (Tree member : members) {
128: String signature;
129: switch (member.getKind()) {
130: case BLOCK:
131: signature = "* "; //NOI18N
132: map.setLastInitializerIndex(index);
133: break;
134: case VARIABLE:
135: signature = "- "; //NOI18N
136: break;
137: case CLASS:
138: signature = "[ "; //NOI18N
139: if (map.getFirstNestedClassIndex() == -1) {
140: map.setFirstNestedClassIndex(index);
141: }
142: break;
143: case METHOD:
144: MethodTree method = (MethodTree) member;
145: boolean hasParams = !method.getParameters().isEmpty();
146: Name methodName = method.getName();
147: if (methodName.contentEquals("<init>")) { //NOI18N
148: signature = hasParams ? "*+" : "* "; //NOI18N
149: map.setLastInitializerIndex(index);
150: } else {
151: if (!hasParams) {
152: if ((map.getSetUpIndex() == -1)
153: && methodName.contentEquals("setUp")) { //NOI18N
154: map.setSetUpIndex(index);
155: }
156: if ((map.getTearDownIndex() == -1)
157: && methodName.contentEquals("tearDown")) { //NOI18N
158: map.setTearDownIndex(index);
159: }
160: }
161: signature = (hasParams ? "!+" : "! ") //NOI18N
162: + methodName.toString();
163: if (map.getFirstMethodIndex() == -1) {
164: map.setFirstMethodIndex(index);
165: }
166:
167: /* annotations: */
168: if (!method.getModifiers().getAnnotations()
169: .isEmpty()) {
170: TreePath methodTreePath = new TreePath(
171: clsTreePath, method);
172: Element methodElement = trees
173: .getElement(methodTreePath);
174: for (AnnotationMirror annMirror : methodElement
175: .getAnnotationMirrors()) {
176: Element annElem = annMirror
177: .getAnnotationType().asElement();
178: assert annElem.getKind() == ElementKind.ANNOTATION_TYPE;
179: assert annElem instanceof TypeElement;
180: String fullName = ((TypeElement) annElem)
181: .getQualifiedName().toString();
182: if (fullName.startsWith(JUNIT4_PKG_PREFIX)) {
183: String shortName = fullName
184: .substring(JUNIT4_PKG_PREFIX
185: .length());
186: int posIndex;
187: if (shortName.equals("Before")) { //NOI18N
188: posIndex = BEFORE_POS_INDEX;
189: } else if (shortName.equals("After")) { //NOI18N
190: posIndex = AFTER_POS_INDEX;
191: } else if (shortName
192: .equals("BeforeClass")) { //NOI18N
193: posIndex = BEFORE_CLASS_POS_INDEX;
194: } else if (shortName
195: .equals("AfterClass")) { //NOI18N
196: posIndex = AFTER_CLASS_POS_INDEX;
197: } else {
198: continue; //next annotation
199: }
200:
201: if (map.positions[posIndex] == -1) {
202: map.positions[posIndex] = index;
203: }
204: }
205: }
206: }
207: }
208: break;
209: default:
210: signature = "x "; //NOI18N
211: }
212: entries.add(signature);
213: index++;
214: }
215:
216: return map;
217: }
218:
219: /**
220: */
221: int getSetUpIndex() {
222: return positions[SETUP_POS_INDEX];
223: }
224:
225: /**
226: */
227: private void setSetUpIndex(int setUpIndex) {
228: positions[SETUP_POS_INDEX] = setUpIndex;
229: }
230:
231: /**
232: */
233: int getTearDownIndex() {
234: return positions[TEARDOWN_POS_INDEX];
235: }
236:
237: /**
238: */
239: private void setTearDownIndex(int tearDownIndex) {
240: positions[TEARDOWN_POS_INDEX] = tearDownIndex;
241: }
242:
243: /**
244: * Returns an index of a method annotated with annotation
245: * {@code org.junit.Before}. If there are more such methods
246: * index of any of these methods may be returned.
247: *
248: * @return index of a {@code @Before}-annotated method,
249: * or {@code -1} if there is no such method
250: */
251: int getBeforeIndex() {
252: return positions[BEFORE_POS_INDEX];
253: }
254:
255: /**
256: */
257: private void setBeforeIndex(int index) {
258: positions[BEFORE_POS_INDEX] = index;
259: }
260:
261: /**
262: * Returns an index of a method annotated with annotation
263: * {@code org.junit.After}. If there are more such methods
264: * index of any of these methods may be returned.
265: *
266: * @return index of a {@code @After}-annotated method,
267: * or {@code -1} if there is no such method
268: */
269: int getAfterIndex() {
270: return positions[AFTER_POS_INDEX];
271: }
272:
273: /**
274: */
275: private void setAfterIndex(int index) {
276: positions[AFTER_POS_INDEX] = index;
277: }
278:
279: /**
280: * Returns an index of a method annotated with annotation
281: * {@code org.junit.BeforeClass}. If there are more such methods
282: * index of any of these methods may be returned.
283: *
284: * @return index of a {@code @BeforeClass}-annotated method,
285: * or {@code -1} if there is no such method
286: */
287: int getBeforeClassIndex() {
288: return positions[BEFORE_CLASS_POS_INDEX];
289: }
290:
291: /**
292: */
293: private void setBeforeClassIndex(int index) {
294: positions[BEFORE_CLASS_POS_INDEX] = index;
295: }
296:
297: /**
298: * Returns an index of a method annotated with annotation
299: * {@code org.junit.AfterClass}. If there are more such methods
300: * index of any of these methods may be returned.
301: *
302: * @return index of a {@code @AfterClass}-annotated method,
303: * or {@code -1} if there is no such method
304: */
305: int getAfterClassIndex() {
306: return positions[AFTER_CLASS_POS_INDEX];
307: }
308:
309: /**
310: */
311: private void setAfterClassIndex(int index) {
312: positions[AFTER_CLASS_POS_INDEX] = index;
313: }
314:
315: /**
316: */
317: int getFirstMethodIndex() {
318: return positions[FIRST_METHOD_POS_INDEX];
319: }
320:
321: /**
322: */
323: private void setFirstMethodIndex(int firstMethodIndex) {
324: positions[FIRST_METHOD_POS_INDEX] = firstMethodIndex;
325: }
326:
327: /**
328: */
329: int getFirstNestedClassIndex() {
330: return positions[FIRST_NESTED_POS_INDEX];
331: }
332:
333: /**
334: */
335: private void setFirstNestedClassIndex(int firstNestedClassIndex) {
336: positions[FIRST_NESTED_POS_INDEX] = firstNestedClassIndex;
337: }
338:
339: /**
340: */
341: int getLastInitializerIndex() {
342: return positions[LAST_INIT_POS_INDEX];
343: }
344:
345: /**
346: */
347: private void setLastInitializerIndex(int lastInitializerIndex) {
348: positions[LAST_INIT_POS_INDEX] = lastInitializerIndex;
349: }
350:
351: /**
352: */
353: boolean containsSetUp() {
354: return getSetUpIndex() != -1;
355: }
356:
357: /**
358: */
359: boolean containsTearDown() {
360: return getTearDownIndex() != -1;
361: }
362:
363: /**
364: */
365: boolean containsBefore() {
366: return getBeforeIndex() != -1;
367: }
368:
369: /**
370: */
371: boolean containsAfter() {
372: return getAfterIndex() != -1;
373: }
374:
375: /**
376: */
377: boolean containsBeforeClass() {
378: return getBeforeClassIndex() != -1;
379: }
380:
381: /**
382: */
383: boolean containsAfterClass() {
384: return getAfterClassIndex() != -1;
385: }
386:
387: /**
388: */
389: boolean containsNoArgMethod(String name) {
390: return findNoArgMethod(name) != -1;
391: }
392:
393: /**
394: */
395: boolean containsMethods() {
396: return getFirstMethodIndex() != -1;
397: }
398:
399: /**
400: */
401: boolean containsInitializers() {
402: return getLastInitializerIndex() != -1;
403: }
404:
405: /**
406: */
407: boolean containsNestedClasses() {
408: return getFirstNestedClassIndex() != -1;
409: }
410:
411: /**
412: */
413: int findNoArgMethod(String name) {
414: if (!containsMethods()) {
415: return -1;
416: }
417: if (name.equals("setUp")) { //NOI18N
418: return getSetUpIndex();
419: }
420: if (name.equals("tearDown")) { //NOI18N
421: return getTearDownIndex();
422: }
423:
424: return signatures.indexOf("! " + name); //NOI18N
425: }
426:
427: /**
428: */
429: void addNoArgMethod(String name) {
430: addNoArgMethod(name, size());
431: }
432:
433: /**
434: */
435: void addNoArgMethod(String name, int index) {
436: int currSize = size();
437:
438: if (index > currSize) {
439: throw new IndexOutOfBoundsException("index: " + index //NOI18N
440: + ", size: " + currSize);//NOI18N
441: }
442:
443: String signature = "! " + name; //NOI18N
444: if (index != currSize) {
445: signatures.add(index, signature);
446: shiftPositions(index, 1);
447: } else {
448: signatures.add(signature); //NOI18N
449: }
450:
451: if (name.equals("setUp")) { //NOI18N
452: setSetUpIndex(index);
453: } else if (name.equals("tearDown")) { //NOI18N
454: setTearDownIndex(index);
455: }
456: if (getFirstMethodIndex() == -1) {
457: setFirstMethodIndex(index);
458: }
459: }
460:
461: /**
462: */
463: void addNoArgMethod(String name, String annotationName) {
464: addNoArgMethod(name, annotationName, size());
465: }
466:
467: /**
468: */
469: void addNoArgMethod(String name, String annotationName, int index) {
470: addNoArgMethod(name, index);
471:
472: if (annotationName.equals(JUnit4TestGenerator.ANN_BEFORE)) {
473: setBeforeIndex(index);
474: } else if (annotationName.equals(JUnit4TestGenerator.ANN_AFTER)) {
475: setAfterIndex(index);
476: } else if (annotationName
477: .equals(JUnit4TestGenerator.ANN_BEFORE_CLASS)) {
478: setBeforeClassIndex(index);
479: } else if (annotationName
480: .equals(JUnit4TestGenerator.ANN_AFTER_CLASS)) {
481: setAfterClassIndex(index);
482: }
483: }
484:
485: /**
486: */
487: void removeNoArgMethod(int index) {
488: if (index < 0) {
489: throw new IndexOutOfBoundsException("negative index (" //NOI18N
490: + index + ')');
491: }
492: if (index >= size()) {
493: throw new IndexOutOfBoundsException("index: " + index //NOI18N
494: + ", size: " + size()); //NOI18N
495: }
496:
497: String signature = signatures.get(index);
498:
499: if (!signature.startsWith("! ")) { //NOI18N
500: throw new IllegalArgumentException(
501: "not a no-arg method at the given index (" //NOI18N
502: + index + ')');
503: }
504:
505: if (index == getSetUpIndex()) {
506: setSetUpIndex(-1);
507: } else if (index == getTearDownIndex()) {
508: setTearDownIndex(-1);
509: }
510: if (index == getFirstMethodIndex()) {
511: int currSize = size();
512: if (index == (currSize - 1)) {
513: setFirstMethodIndex(-1);
514: } else {
515: int newFirstMethodIndex = -1;
516: int memberIndex = index + 1;
517: for (String sign : signatures.subList(index + 1,
518: currSize)) {
519: if (sign.startsWith("! ")) {
520: newFirstMethodIndex = memberIndex;
521: break;
522: }
523: memberIndex++;
524: }
525: setFirstMethodIndex(newFirstMethodIndex);
526: }
527: }
528: shiftPositions(index + 1, -1);
529: }
530:
531: /**
532: * Returns names of all no-argument methods.
533: *
534: * @return list of names of no-argument methods
535: * in the order of the methods in the source code
536: */
537: List<String> getNoArgMethods() {
538: if (!containsMethods()) {
539: return Collections.<String> emptyList();
540: }
541:
542: List<String> result = new ArrayList<String>(signatures.size());
543: for (String signature : signatures) {
544: if (signature.startsWith("! ")) { //NOI18N
545: result.add(signature.substring(2));
546: }
547: }
548: return result.isEmpty() ? Collections.<String> emptyList()
549: : result;
550: }
551:
552: /**
553: */
554: int size() {
555: return signatures.size();
556: }
557:
558: /**
559: */
560: private void shiftPositions(int fromIndex, int shiftSize) {
561: for (int i = 0; i < positions.length; i++) {
562: int pos = positions[i];
563: if ((pos != -1) && (pos >= fromIndex)) {
564: positions[i] = pos + shiftSize;
565: }
566: }
567: }
568:
569: }
|