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: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans.modules.junit;
041:
042: import java.util.ArrayList;
043: import java.util.Arrays;
044: import java.util.BitSet;
045: import java.util.Collection;
046: import java.util.Collections;
047: import java.util.HashMap;
048: import java.util.HashSet;
049: import java.util.List;
050: import java.util.Map;
051: import javax.lang.model.element.Element;
052: import javax.lang.model.element.ExecutableElement;
053: import javax.lang.model.element.TypeElement;
054: import javax.lang.model.element.VariableElement;
055: import javax.lang.model.type.TypeMirror;
056: import javax.lang.model.util.ElementFilter;
057: import javax.lang.model.util.Elements;
058: import org.netbeans.api.java.source.WorkingCopy;
059: import static java.lang.Boolean.TRUE;
060:
061: /**
062: * Utility class for generating a collection of unique names of test methods.
063: *
064: * @author Marian Petras
065: */
066: final class TestMethodNameGenerator {
067:
068: /** maximum number of parameter-type method name suffixes */
069: private static final int MAX_SUFFIX_TYPES = 2;
070:
071: /**
072: * collection of reserved type identifiers that should be left unchanged.
073: * A method name may be reserved because it was assigned to some method
074: * (value {@code Boolean.TRUE}) or because it was inherited from some parent
075: * class (value {@code Boolean.FALSE}).
076: */
077: private Collection<String> reservedNames;
078:
079: private final WorkingCopy workingCopy;
080: private final List<ExecutableElement> srcMethods;
081: private final TypeElement tstClassElem;
082: private final List<ExecutableElement> existingMethods;
083:
084: private final String[] testMethodNames;
085:
086: private TestMethodNameGenerator(
087: final List<ExecutableElement> srcMethods,
088: final TypeElement tstClassElem, WorkingCopy workingCopy) {
089: this .srcMethods = srcMethods;
090: this .tstClassElem = tstClassElem;
091: this .workingCopy = workingCopy;
092:
093: existingMethods = (tstClassElem != null) ? getExistingMethods(tstClassElem)
094: : Collections.<ExecutableElement> emptyList();
095: reservedNames = new HashSet<String>(
096: (existingMethods.size() * 3 + 1) / 2);
097:
098: testMethodNames = new String[srcMethods.size()];
099: }
100:
101: /**
102: * Generates a list of unique names of test methods for the given source
103: * methods. The names are generated such that they do not conflict
104: * with names of methods that are already present in the test class
105: *
106: * @param srcMethods source methods for which test methods are about
107: * to be created - names of these test methods will
108: * be generated
109: * @param tstClassElem test class - the existing test class in which
110: * new test classes are about to be generated,
111: * or {@code null} if the test class does not exist yet
112: * @param reservedMethodNames list of reserved test method names
113: * - these should not be avoided
114: * by the test method name generator;
115: * it may be {@code null} if there are no
116: * reserved method names
117: * @return list of names for test methods for the given source methods;
118: * the names in the list are unique, they do not conflict
119: * with names of existing methods in the given test class (if any)
120: * and they are stored in the order of the source methods
121: */
122: static List<String> getTestMethodNames(
123: final List<ExecutableElement> srcMethods,
124: final TypeElement tstClassElem,
125: final Collection<String> reservedMethodNames,
126: final WorkingCopy workingCopy) {
127: TestMethodNameGenerator inst = new TestMethodNameGenerator(
128: srcMethods, tstClassElem, workingCopy);
129: if (reservedMethodNames != null) {
130: inst.reservedNames.addAll(reservedMethodNames);
131: }
132: return inst.getTestMethodNames();
133: }
134:
135: /**
136: * Determines names for test methods that are about to be generated.
137: *
138: * @return list of test method names, in the order corresponding to the
139: * order of {@linkplain #srcMethods source methods}
140: */
141: private List<String> getTestMethodNames() {
142: if (tstClassElem != null) {
143: collectExistingMethodNames(tstClassElem, reservedNames);
144: }
145:
146: final int methodsCount = srcMethods.size();
147: final String[] result = new String[methodsCount];
148:
149: final Map<String, Object> namesUsage = new HashMap<String, Object>(
150: methodsCount * 3 + 1 / 2);
151: final BitSet conflicting = new BitSet(methodsCount);
152: int conflictingCount = 0;
153:
154: /*
155: * Identify methods with overloaded names:
156: */
157: int index = -1;
158: assert namesUsage.isEmpty();
159: for (ExecutableElement srcMethod : srcMethods) {
160:
161: index++;
162:
163: String srcMethodName = srcMethod.getSimpleName().toString();
164: String testMethodName = buildTestMethodName(srcMethodName);
165:
166: testMethodNames[index] = testMethodName;
167:
168: conflictingCount += registerTestMethodName(testMethodName,
169: index, namesUsage, conflicting);
170: }
171: namesUsage.clear();
172:
173: assert conflictingCount <= methodsCount;
174: assert conflictingCount == conflicting.cardinality();
175:
176: int uniqueCount = methodsCount - conflictingCount;
177: if (uniqueCount > 0) {
178:
179: /* fixate all unique method names... */
180:
181: for (index = conflicting.nextClearBit(0); //for all unique...
182: (index >= 0) && (index < methodsCount); index = conflicting
183: .nextClearBit(index + 1)) {
184: String name = testMethodNames[index];
185: result[index] = name;
186: reservedNames.add(name); //fixate
187: }
188:
189: }
190:
191: /* ... try to resolve the conflicting ones... */
192:
193: int[] paramsCount = null;
194: Collection<TypeMirror> paramTypes = null;
195:
196: if (conflictingCount > 0) {
197:
198: /* will hold number of parameters of each source method */
199: paramsCount = new int[srcMethods.size()];
200: paramTypes = collectParamTypes(paramsCount);
201:
202: /* ROUND #2 - check conflicts of test methods of no-arg source methods */
203:
204: BitSet tested = findNoArgMethods(paramsCount, conflicting);
205: BitSet noArgConflicting = new BitSet(srcMethods.size());
206:
207: final int testedCount = tested.cardinality();
208:
209: assert namesUsage.isEmpty();
210: int noArgConflictingCount = 0;
211:
212: for (index = tested.nextSetBit(0); index >= 0; index = tested
213: .nextSetBit(index + 1)) {
214:
215: noArgConflictingCount += registerTestMethodName(
216: testMethodNames[index], index, namesUsage,
217: noArgConflicting);
218: }
219: namesUsage.clear();
220:
221: assert noArgConflictingCount <= testedCount;
222: assert noArgConflictingCount == noArgConflicting
223: .cardinality();
224:
225: int noArgUniqueCount = methodsCount - conflictingCount;
226: if (noArgUniqueCount > 0) { /* among those for no-arg methods */
227:
228: /* fixate all unique names of test methods for no-arg source method: */
229:
230: BitSet noArgUnique = new BitSet(tested.size());
231: noArgUnique.or(tested);
232: noArgUnique.andNot(noArgConflicting);
233:
234: for (index = noArgUnique.nextSetBit(0); index >= 0; index = noArgUnique
235: .nextSetBit(index + 1)) {
236: String name = testMethodNames[index];
237: result[index] = name;
238: reservedNames.add(name); //fixate
239: }
240: }
241: if (noArgConflictingCount > 0) {/* among those for no-arg methods */
242:
243: /* resolve conflicting names of test methods for no-arg source methods: */
244:
245: Map<String, Integer> usageNumbers = new HashMap<String, Integer>(
246: (noArgConflictingCount + 1) * 3 / 2);
247:
248: noArgConflicting = tested;
249:
250: for (index = noArgConflicting.nextSetBit(0); index >= 0; index = noArgConflicting
251: .nextSetBit(index + 1)) {
252:
253: String simpleName = testMethodNames[index];
254: Integer oldValue = usageNumbers.get(simpleName);
255: int suffix = (oldValue == null) ? 0 : oldValue
256: .intValue();
257: String numberedName;
258: do {
259: suffix++;
260: numberedName = simpleName + suffix;
261: } while (reservedNames.contains(numberedName));
262: usageNumbers.put(simpleName, Integer
263: .valueOf(suffix));
264:
265: /* fixate immediately to ensure thare are really no conflicts */
266: result[index] = numberedName;
267: reservedNames.add(numberedName); //fixate
268: }
269:
270: }
271:
272: /*
273: * OK, now we know that names of test methods for no-arg source
274: * methods are resolved.
275: */
276: conflicting.andNot(noArgConflicting);
277: conflictingCount -= noArgConflictingCount;
278: uniqueCount += noArgConflictingCount;
279: assert conflictingCount + uniqueCount == methodsCount;
280: }
281:
282: String[] typeIdSuffixes = null;
283: String[] parCntSuffixes = null;
284:
285: if (conflictingCount > 0) {
286:
287: /*
288: * ROUND #3 - try to distinguish test method names by appending
289: * identifiers of the source method parameter types or, if there
290: * are too many parameters, by appending the number of parameters
291: */
292:
293: BitSet tested = (BitSet) conflicting.clone();
294: int testedCount = conflictingCount;
295: conflicting.clear();
296: conflictingCount = 0;
297:
298: assert paramsCount != null;
299: assert paramTypes != null;
300:
301: /* only needed for methods with low number of arguments */
302: TypeNameIdGenerator typeIdGenerator = null;
303:
304: if (!paramTypes.isEmpty()) {
305: typeIdGenerator = TypeNameIdGenerator.createFor(
306: paramTypes, workingCopy.getElements(),
307: workingCopy.getTypes());
308: }
309:
310: assert namesUsage.isEmpty();
311: String[] methodNames = new String[methodsCount];
312: typeIdSuffixes = new String[methodsCount];
313: parCntSuffixes = new String[methodsCount];
314: for (index = tested.nextSetBit(0); index >= 0; index = tested
315: .nextSetBit(index + 1)) {
316:
317: int parCount = paramsCount[index];
318: String suffix;
319: if (parCount > MAX_SUFFIX_TYPES) {
320: suffix = parCntSuffixes[index] = makeParamCountSuffix(parCount);
321: } else {
322: List<? extends VariableElement> params = srcMethods
323: .get(index).getParameters();
324: StringBuilder buf = new StringBuilder(40);
325: for (int i = 0; i < parCount; i++) {
326: buf.append('_');
327: buf
328: .append(typeIdGenerator
329: .getParamTypeId(params.get(i)
330: .asType()));
331: }
332: suffix = typeIdSuffixes[index] = buf.toString();
333: }
334:
335: String methodName = methodNames[index] = testMethodNames[index]
336: + suffix;
337:
338: /* check whether it is duplicite: */
339: conflictingCount += registerTestMethodName(methodName,
340: index, namesUsage, conflicting);
341: }
342: namesUsage.clear();
343:
344: uniqueCount = testedCount - conflictingCount;
345:
346: if (uniqueCount > 0) {
347:
348: /* fixate all new unique names */
349:
350: BitSet unique = (BitSet) tested.clone();
351: unique.andNot(conflicting);
352: assert unique.cardinality() == uniqueCount;
353:
354: for (index = unique.nextSetBit(0); index >= 0; index = unique
355: .nextSetBit(index + 1)) {
356: String methodName = methodNames[index];
357: result[index] = methodName;
358: reservedNames.add(methodName); //fixate
359: }
360: }
361: }
362:
363: if (conflictingCount > 0) {
364:
365: /*
366: * ROUND #4 - try to distinguish test method names by appending
367: * identifiers of the source method parameter types and their types,
368: * or, if there are too many parameters, by only appending
369: * the number of parameters
370: */
371:
372: assert typeIdSuffixes != null;
373: assert parCntSuffixes != null;
374:
375: BitSet tested = (BitSet) conflicting.clone();
376: int testedCount = conflictingCount;
377: conflicting.clear();
378: conflictingCount = 0;
379:
380: assert namesUsage.isEmpty();
381: String[] methodNames = new String[methodsCount];
382: for (index = tested.nextSetBit(0); index >= 0; index = tested
383: .nextSetBit(index + 1)) {
384:
385: int parCount = paramsCount[index];
386:
387: StringBuilder buf = new StringBuilder(60);
388: buf.append(testMethodNames[index]);
389: if (parCount <= MAX_SUFFIX_TYPES) {
390: assert typeIdSuffixes[index] != null;
391: buf.append(typeIdSuffixes[index]);
392: }
393: String parCntSuffix = parCntSuffixes[index];
394: if (parCntSuffix == null) {
395: assert (parCount <= MAX_SUFFIX_TYPES);
396: parCntSuffix = parCntSuffixes[index] = makeParamCountSuffix(parCount);
397: }
398: buf.append(parCntSuffix);
399:
400: String methodName = methodNames[index] = buf.toString();
401:
402: /* check whether it is duplicite: */
403: conflictingCount += registerTestMethodName(methodName,
404: index, namesUsage, conflicting);
405: }
406: namesUsage.clear();
407:
408: uniqueCount = testedCount - conflictingCount;
409:
410: if (uniqueCount > 0) {
411:
412: /* fixate all new unique names */
413:
414: BitSet unique = (BitSet) tested.clone();
415: unique.andNot(conflicting);
416: assert unique.cardinality() == uniqueCount;
417:
418: for (index = unique.nextSetBit(0); index >= 0; index = unique
419: .nextSetBit(index + 1)) {
420: String methodName = methodNames[index];
421: result[index] = methodName;
422: reservedNames.add(methodName); //fixate
423: }
424: }
425: }
426:
427: if (conflictingCount > 0) {
428:
429: /* ROUND #5 - append number of parameters + sequential number */
430:
431: Map<String, Integer> usageNumbers = new HashMap<String, Integer>(
432: (conflictingCount * 3 + 1) / 2);
433:
434: assert parCntSuffixes != null;
435:
436: for (index = conflicting.nextSetBit(0); index >= 0; index = conflicting
437: .nextSetBit(index + 1)) {
438:
439: String noNumMethodName = testMethodNames[index]
440: + parCntSuffixes[index] + '_';
441: Integer oldValue = usageNumbers.get(noNumMethodName);
442: int suffix = (oldValue == null) ? 0 : oldValue
443: .intValue();
444: String methodName;
445: do {
446: suffix++;
447: methodName = noNumMethodName + suffix;
448: } while (reservedNames.contains(methodName));
449: usageNumbers.put(methodName, Integer.valueOf(suffix));
450:
451: /* fixate immediately to ensure thare are really no conflicts */
452: result[index] = methodName;
453: reservedNames.add(methodName); //fixate
454: }
455: }
456:
457: return Arrays.asList(result);
458: }
459:
460: /**
461: */
462: private static final String makeParamCountSuffix(int paramCount) {
463: return new StringBuilder(8).append('_').append(paramCount)
464: .append("args") //NOI18N
465: .toString();
466: }
467:
468: /**
469: *
470: */
471: private int registerTestMethodName(String testMethodName,
472: int index, Map<String, Object> namesUsage,
473: BitSet conflictingNamesIndices) {
474: Object oldValue = namesUsage.put(testMethodName, Integer
475: .valueOf(index));
476: boolean nameConflict = (oldValue != null)
477: || (reservedNames != null)
478: && (reservedNames.contains(testMethodName));
479:
480: assert !conflictingNamesIndices.get(index);
481:
482: int rv = 0;
483: if (nameConflict) {
484: if ((oldValue != null) && (oldValue != TRUE)) {
485: /*
486: * (oldValue == Integer) ... conflict with another method name
487: * detected
488: * (oldValue == null) ...... conflict with a reserved method
489: * name detected
490: * (oldValue == TRUE) ...... name has been already known to be
491: * in conflict with some other name
492: */
493: assert (oldValue.getClass() == Integer.class);
494: int conflictingNameIndex = ((Integer) oldValue)
495: .intValue();
496: assert !conflictingNamesIndices
497: .get(conflictingNameIndex);
498: conflictingNamesIndices.set(conflictingNameIndex);
499: rv++;
500: }
501: conflictingNamesIndices.set(index);
502: namesUsage.put(testMethodName, TRUE);
503: rv++;
504: }
505: return rv;
506: }
507:
508: /**
509: * Collects names of accessible no-argument methods that are present
510: * in the given class and its superclasses. Methods inherited from the
511: * class's superclasses are taken into account, too.
512: *
513: * @param clazz class whose methods' names should be collected
514: * @param reservedMethodNames collection to which the method names
515: * should be added
516: */
517: private void collectExistingMethodNames(TypeElement clazz,
518: Collection<String> reservedMethodNames) {
519: final Elements elements = workingCopy.getElements();
520: List<? extends Element> allMembers = elements
521: .getAllMembers(clazz);
522: List<? extends ExecutableElement> methods = ElementFilter
523: .methodsIn(allMembers);
524: if (!methods.isEmpty()) {
525: for (ExecutableElement method : methods) {
526: if (method.getParameters().isEmpty()) {
527: reservedMethodNames.add(method.getSimpleName()
528: .toString());
529: }
530: }
531: }
532: }
533:
534: /**
535: * Collects types of parameters used by source methods.
536: * Methods without parameters and methods having more than
537: * {@value #MAX_SUFFIX_TYPES} parameters are skipped.
538: * This method also stores number of parameters of each method to the given
539: * array.
540: *
541: * @param paramCount an empty array of numbers - its length must be equal
542: * to the number of {@link #srcMethods}
543: * @return types of parameters of methods having an overloaded name
544: */
545: private Collection<TypeMirror> collectParamTypes(int[] paramsCount) {
546: Collection<TypeMirror> paramTypes = new ArrayList<TypeMirror>(
547: srcMethods.size());
548: int index = -1;
549: for (ExecutableElement srcMethod : srcMethods) {
550:
551: index++;
552:
553: List<? extends VariableElement> params = srcMethod
554: .getParameters();
555: if (params.isEmpty()) {
556: paramsCount[index] = 0;
557: continue;
558: }
559:
560: final int parCount;
561: paramsCount[index] = (parCount = params.size());
562:
563: if (parCount <= MAX_SUFFIX_TYPES) {
564: for (int i = 0; i < parCount; i++) {
565: paramTypes.add(params.get(i).asType());
566: }
567: }
568: }
569: return !paramTypes.isEmpty() ? paramTypes : Collections
570: .<TypeMirror> emptyList();
571: }
572:
573: /**
574: * Returns a list of methods contained directly in the given class.
575: *
576: * @param classElem class whose methods should be returned
577: * @return list of methods in the given class
578: */
579: private static List<ExecutableElement> getExistingMethods(
580: final TypeElement classElem) {
581: List<? extends Element> elements = classElem
582: .getEnclosedElements();
583: if (elements.isEmpty()) {
584: return Collections.<ExecutableElement> emptyList();
585: }
586:
587: List<ExecutableElement> methods = ElementFilter
588: .methodsIn(elements);
589: return !methods.isEmpty() ? methods : Collections
590: .<ExecutableElement> emptyList();
591: }
592:
593: private static String buildTestMethodName(String srcMethodName) {
594: int length = srcMethodName.length();
595: StringBuilder buf = new StringBuilder(length + 4);
596: buf.append("test"); //NOI18N
597: buf.append(Character.toUpperCase(srcMethodName.charAt(0)));
598: if (length != 1) {
599: buf.append(srcMethodName.substring(1));
600: }
601: return buf.toString();
602: }
603:
604: /**
605: * Finds indices of test methods which have conflicting names and test
606: * no-argument source methods.
607: *
608: * @param paramsCount array containing number of parameters of each source
609: * method
610: * @param conflicting bitmap - set bits determine indices of test methods
611: * having conflicting names
612: * @return bitmap which holds information which of these conflicting test
613: * methods is a test for a no-argument source method
614: */
615: private static BitSet findNoArgMethods(int[] paramsCount,
616: BitSet conflicting) {
617: BitSet result = new BitSet(paramsCount.length);
618: for (int index = 0; index < paramsCount.length; index++) {
619: if ((paramsCount[index] == 0) && conflicting.get(index)) {
620: result.set(index);
621: }
622: }
623: return result;
624: }
625:
626: }
|