| /* |
| * 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.junit; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| import javax.lang.model.util.Elements; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import static java.lang.Boolean.TRUE; |
| |
| /** |
| * Utility class for generating a collection of unique names of test methods. |
| * |
| * @author Marian Petras |
| */ |
| final class TestMethodNameGenerator { |
| |
| /** maximum number of parameter-type method name suffixes */ |
| private static final int MAX_SUFFIX_TYPES = 2; |
| |
| /** |
| * collection of reserved type identifiers that should be left unchanged. |
| * A method name may be reserved because it was assigned to some method |
| * (value {@code Boolean.TRUE}) or because it was inherited from some parent |
| * class (value {@code Boolean.FALSE}). |
| */ |
| private Collection<String> reservedNames; |
| |
| private final WorkingCopy workingCopy; |
| private final List<ExecutableElement> srcMethods; |
| private final TypeElement tstClassElem; |
| private final List<ExecutableElement> existingMethods; |
| |
| private final String[] testMethodNames; |
| |
| private TestMethodNameGenerator(final List<ExecutableElement> srcMethods, |
| final TypeElement tstClassElem, |
| WorkingCopy workingCopy) { |
| this.srcMethods = srcMethods; |
| this.tstClassElem = tstClassElem; |
| this.workingCopy = workingCopy; |
| |
| existingMethods = (tstClassElem != null) |
| ? getExistingMethods(tstClassElem) |
| : Collections.<ExecutableElement>emptyList(); |
| reservedNames = new HashSet<String>((existingMethods.size() * 3 + 1) / 2); |
| |
| testMethodNames = new String[srcMethods.size()]; |
| } |
| |
| /** |
| * Generates a list of unique names of test methods for the given source |
| * methods. The names are generated such that they do not conflict |
| * with names of methods that are already present in the test class |
| * |
| * @param srcMethods source methods for which test methods are about |
| * to be created - names of these test methods will |
| * be generated |
| * @param tstClassElem test class - the existing test class in which |
| * new test classes are about to be generated, |
| * or {@code null} if the test class does not exist yet |
| * @param reservedMethodNames list of reserved test method names |
| * - these should not be avoided |
| * by the test method name generator; |
| * it may be {@code null} if there are no |
| * reserved method names |
| * @return list of names for test methods for the given source methods; |
| * the names in the list are unique, they do not conflict |
| * with names of existing methods in the given test class (if any) |
| * and they are stored in the order of the source methods |
| */ |
| static List<String> getTestMethodNames( |
| final List<ExecutableElement> srcMethods, |
| final TypeElement tstClassElem, |
| final Collection<String> reservedMethodNames, |
| final WorkingCopy workingCopy) { |
| TestMethodNameGenerator inst |
| = new TestMethodNameGenerator(srcMethods, tstClassElem, workingCopy); |
| if (reservedMethodNames != null) { |
| inst.reservedNames.addAll(reservedMethodNames); |
| } |
| return inst.getTestMethodNames(); |
| } |
| |
| /** |
| * Determines names for test methods that are about to be generated. |
| * |
| * @return list of test method names, in the order corresponding to the |
| * order of {@linkplain #srcMethods source methods} |
| */ |
| private List<String> getTestMethodNames() { |
| if (tstClassElem != null) { |
| collectExistingMethodNames(tstClassElem, reservedNames); |
| } |
| |
| final int methodsCount = srcMethods.size(); |
| final String[] result = new String[methodsCount]; |
| |
| final Map<String, Object> namesUsage |
| = new HashMap<String, Object>(methodsCount * 3 + 1 / 2); |
| final BitSet conflicting = new BitSet(methodsCount); |
| int conflictingCount = 0; |
| |
| /* |
| * Identify methods with overloaded names: |
| */ |
| int index = -1; |
| assert namesUsage.isEmpty(); |
| for (ExecutableElement srcMethod : srcMethods) { |
| |
| index++; |
| |
| String srcMethodName = srcMethod.getSimpleName().toString(); |
| String testMethodName = buildTestMethodName(srcMethodName); |
| |
| testMethodNames[index] = testMethodName; |
| |
| conflictingCount += registerTestMethodName(testMethodName, |
| index, |
| namesUsage, |
| conflicting); |
| } |
| namesUsage.clear(); |
| |
| assert conflictingCount <= methodsCount; |
| assert conflictingCount == conflicting.cardinality(); |
| |
| int uniqueCount = methodsCount - conflictingCount; |
| if (uniqueCount > 0) { |
| |
| /* fixate all unique method names... */ |
| |
| for (index = conflicting.nextClearBit(0); //for all unique... |
| (index >= 0) && (index < methodsCount); |
| index = conflicting.nextClearBit(index + 1)) { |
| String name = testMethodNames[index]; |
| result[index] = name; |
| reservedNames.add(name); //fixate |
| } |
| |
| } |
| |
| /* ... try to resolve the conflicting ones... */ |
| |
| int[] paramsCount = null; |
| Collection<TypeMirror> paramTypes = null; |
| |
| if (conflictingCount > 0) { |
| |
| /* will hold number of parameters of each source method */ |
| paramsCount = new int[srcMethods.size()]; |
| paramTypes = collectParamTypes(paramsCount); |
| |
| /* ROUND #2 - check conflicts of test methods of no-arg source methods */ |
| |
| BitSet tested = findNoArgMethods(paramsCount, conflicting); |
| BitSet noArgConflicting = new BitSet(srcMethods.size()); |
| |
| final int testedCount = tested.cardinality(); |
| |
| assert namesUsage.isEmpty(); |
| int noArgConflictingCount = 0; |
| |
| for (index = tested.nextSetBit(0); |
| index >= 0; |
| index = tested.nextSetBit(index + 1)) { |
| |
| noArgConflictingCount += registerTestMethodName( |
| testMethodNames[index], |
| index, |
| namesUsage, |
| noArgConflicting); |
| } |
| namesUsage.clear(); |
| |
| assert noArgConflictingCount <= testedCount; |
| assert noArgConflictingCount == noArgConflicting.cardinality(); |
| |
| int noArgUniqueCount = methodsCount - conflictingCount; |
| if (noArgUniqueCount > 0) { /* among those for no-arg methods */ |
| |
| /* fixate all unique names of test methods for no-arg source method: */ |
| |
| BitSet noArgUnique = new BitSet(tested.size()); |
| noArgUnique.or(tested); |
| noArgUnique.andNot(noArgConflicting); |
| |
| for (index = noArgUnique.nextSetBit(0); |
| index >= 0; |
| index = noArgUnique.nextSetBit(index + 1)) { |
| String name = testMethodNames[index]; |
| result[index] = name; |
| reservedNames.add(name); //fixate |
| } |
| } |
| if (noArgConflictingCount > 0) {/* among those for no-arg methods */ |
| |
| /* resolve conflicting names of test methods for no-arg source methods: */ |
| |
| Map<String, Integer> usageNumbers |
| = new HashMap<String, Integer>((noArgConflictingCount + 1) * 3 / 2); |
| |
| noArgConflicting = tested; |
| |
| for (index = noArgConflicting.nextSetBit(0); |
| index >= 0; |
| index = noArgConflicting.nextSetBit(index + 1)) { |
| |
| String simpleName = testMethodNames[index]; |
| Integer oldValue = usageNumbers.get(simpleName); |
| int suffix = (oldValue == null) |
| ? 0 |
| : oldValue.intValue(); |
| String numberedName; |
| do { |
| suffix++; |
| numberedName = simpleName + suffix; |
| } while (reservedNames.contains(numberedName)); |
| usageNumbers.put(simpleName, Integer.valueOf(suffix)); |
| |
| /* fixate immediately to ensure thare are really no conflicts */ |
| result[index] = numberedName; |
| reservedNames.add(numberedName); //fixate |
| } |
| |
| } |
| |
| /* |
| * OK, now we know that names of test methods for no-arg source |
| * methods are resolved. |
| */ |
| conflicting.andNot(noArgConflicting); |
| conflictingCount -= noArgConflictingCount; |
| uniqueCount += noArgConflictingCount; |
| assert conflictingCount + uniqueCount == methodsCount; |
| } |
| |
| String[] typeIdSuffixes = null; |
| String[] parCntSuffixes = null; |
| |
| if (conflictingCount > 0) { |
| |
| /* |
| * ROUND #3 - try to distinguish test method names by appending |
| * identifiers of the source method parameter types or, if there |
| * are too many parameters, by appending the number of parameters |
| */ |
| |
| BitSet tested = (BitSet) conflicting.clone(); |
| int testedCount = conflictingCount; |
| conflicting.clear(); |
| conflictingCount = 0; |
| |
| assert paramsCount != null; |
| assert paramTypes != null; |
| |
| /* only needed for methods with low number of arguments */ |
| TypeNameIdGenerator typeIdGenerator = null; |
| |
| if (!paramTypes.isEmpty()) { |
| typeIdGenerator = TypeNameIdGenerator.createFor( |
| paramTypes, |
| workingCopy.getElements(), |
| workingCopy.getTypes()); |
| } |
| |
| assert namesUsage.isEmpty(); |
| String[] methodNames = new String[methodsCount]; |
| typeIdSuffixes = new String[methodsCount]; |
| parCntSuffixes = new String[methodsCount]; |
| for (index = tested.nextSetBit(0); |
| index >= 0; |
| index = tested.nextSetBit(index + 1)) { |
| |
| int parCount = paramsCount[index]; |
| String suffix; |
| if (parCount > MAX_SUFFIX_TYPES) { |
| suffix = parCntSuffixes[index] = makeParamCountSuffix(parCount); |
| } else { |
| List<? extends VariableElement> params |
| = srcMethods.get(index).getParameters(); |
| StringBuilder buf = new StringBuilder(40); |
| for (int i = 0; i < parCount; i++) { |
| buf.append('_'); |
| buf.append(typeIdGenerator.getParamTypeId(params.get(i).asType())); |
| } |
| suffix = typeIdSuffixes[index] = buf.toString(); |
| } |
| |
| String methodName = methodNames[index] = testMethodNames[index] + suffix; |
| |
| /* check whether it is duplicite: */ |
| conflictingCount += registerTestMethodName( |
| methodName, |
| index, |
| namesUsage, |
| conflicting); |
| } |
| namesUsage.clear(); |
| |
| uniqueCount = testedCount - conflictingCount; |
| |
| if (uniqueCount > 0) { |
| |
| /* fixate all new unique names */ |
| |
| BitSet unique = (BitSet) tested.clone(); |
| unique.andNot(conflicting); |
| assert unique.cardinality() == uniqueCount; |
| |
| for (index = unique.nextSetBit(0); |
| index >= 0; |
| index = unique.nextSetBit(index + 1)) { |
| String methodName = methodNames[index]; |
| result[index] = methodName; |
| reservedNames.add(methodName); //fixate |
| } |
| } |
| } |
| |
| if (conflictingCount > 0) { |
| |
| /* |
| * ROUND #4 - try to distinguish test method names by appending |
| * identifiers of the source method parameter types and their types, |
| * or, if there are too many parameters, by only appending |
| * the number of parameters |
| */ |
| |
| assert typeIdSuffixes != null; |
| assert parCntSuffixes != null; |
| |
| BitSet tested = (BitSet) conflicting.clone(); |
| int testedCount = conflictingCount; |
| conflicting.clear(); |
| conflictingCount = 0; |
| |
| assert namesUsage.isEmpty(); |
| String[] methodNames = new String[methodsCount]; |
| for (index = tested.nextSetBit(0); |
| index >= 0; |
| index = tested.nextSetBit(index + 1)) { |
| |
| int parCount = paramsCount[index]; |
| |
| StringBuilder buf = new StringBuilder(60); |
| buf.append(testMethodNames[index]); |
| if (parCount <= MAX_SUFFIX_TYPES) { |
| assert typeIdSuffixes[index] != null; |
| buf.append(typeIdSuffixes[index]); |
| } |
| String parCntSuffix = parCntSuffixes[index]; |
| if (parCntSuffix == null) { |
| assert (parCount <= MAX_SUFFIX_TYPES); |
| parCntSuffix = parCntSuffixes[index] = makeParamCountSuffix(parCount); |
| } |
| buf.append(parCntSuffix); |
| |
| String methodName = methodNames[index] = buf.toString(); |
| |
| /* check whether it is duplicite: */ |
| conflictingCount += registerTestMethodName( |
| methodName, |
| index, |
| namesUsage, |
| conflicting); |
| } |
| namesUsage.clear(); |
| |
| uniqueCount = testedCount - conflictingCount; |
| |
| if (uniqueCount > 0) { |
| |
| /* fixate all new unique names */ |
| |
| BitSet unique = (BitSet) tested.clone(); |
| unique.andNot(conflicting); |
| assert unique.cardinality() == uniqueCount; |
| |
| for (index = unique.nextSetBit(0); |
| index >= 0; |
| index = unique.nextSetBit(index + 1)) { |
| String methodName = methodNames[index]; |
| result[index] = methodName; |
| reservedNames.add(methodName); //fixate |
| } |
| } |
| } |
| |
| if (conflictingCount > 0) { |
| |
| |
| /* ROUND #5 - append number of parameters + sequential number */ |
| |
| Map<String, Integer> usageNumbers |
| = new HashMap<String, Integer>((conflictingCount * 3 + 1) / 2); |
| |
| assert parCntSuffixes != null; |
| |
| for (index = conflicting.nextSetBit(0); |
| index >= 0; |
| index = conflicting.nextSetBit(index + 1)) { |
| |
| String noNumMethodName = testMethodNames[index] + parCntSuffixes[index] + '_'; |
| Integer oldValue = usageNumbers.get(noNumMethodName); |
| int suffix = (oldValue == null) |
| ? 0 |
| : oldValue.intValue(); |
| String methodName; |
| do { |
| suffix++; |
| methodName = noNumMethodName + suffix; |
| } while (reservedNames.contains(methodName)); |
| usageNumbers.put(methodName, Integer.valueOf(suffix)); |
| |
| /* fixate immediately to ensure thare are really no conflicts */ |
| result[index] = methodName; |
| reservedNames.add(methodName); //fixate |
| } |
| } |
| |
| return Arrays.asList(result); |
| } |
| |
| /** |
| */ |
| private static final String makeParamCountSuffix(int paramCount) { |
| return new StringBuilder(8) |
| .append('_') |
| .append(paramCount) |
| .append("args") //NOI18N |
| .toString(); |
| } |
| |
| /** |
| * |
| */ |
| private int registerTestMethodName(String testMethodName, |
| int index, |
| Map<String, Object>namesUsage, |
| BitSet conflictingNamesIndices) { |
| Object oldValue = namesUsage.put(testMethodName, Integer.valueOf(index)); |
| boolean nameConflict = (oldValue != null) |
| || (reservedNames != null) |
| && (reservedNames.contains(testMethodName)); |
| |
| assert !conflictingNamesIndices.get(index); |
| |
| int rv = 0; |
| if (nameConflict) { |
| if ((oldValue != null) && (oldValue != TRUE)) { |
| /* |
| * (oldValue == Integer) ... conflict with another method name |
| * detected |
| * (oldValue == null) ...... conflict with a reserved method |
| * name detected |
| * (oldValue == TRUE) ...... name has been already known to be |
| * in conflict with some other name |
| */ |
| assert (oldValue.getClass() == Integer.class); |
| int conflictingNameIndex = ((Integer) oldValue).intValue(); |
| assert !conflictingNamesIndices.get(conflictingNameIndex); |
| conflictingNamesIndices.set(conflictingNameIndex); |
| rv++; |
| } |
| conflictingNamesIndices.set(index); |
| namesUsage.put(testMethodName, TRUE); |
| rv++; |
| } |
| return rv; |
| } |
| |
| /** |
| * Collects names of accessible no-argument methods that are present |
| * in the given class and its superclasses. Methods inherited from the |
| * class's superclasses are taken into account, too. |
| * |
| * @param clazz class whose methods' names should be collected |
| * @param reservedMethodNames collection to which the method names |
| * should be added |
| */ |
| private void collectExistingMethodNames(TypeElement clazz, |
| Collection<String> reservedMethodNames) { |
| final Elements elements = workingCopy.getElements(); |
| List<? extends Element> allMembers = elements.getAllMembers(clazz); |
| List<? extends ExecutableElement> methods = ElementFilter.methodsIn(allMembers); |
| if (!methods.isEmpty()) { |
| for (ExecutableElement method : methods) { |
| if (method.getParameters().isEmpty()) { |
| reservedMethodNames.add(method.getSimpleName().toString()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Collects types of parameters used by source methods. |
| * Methods without parameters and methods having more than |
| * {@value #MAX_SUFFIX_TYPES} parameters are skipped. |
| * This method also stores number of parameters of each method to the given |
| * array. |
| * |
| * @param paramCount an empty array of numbers - its length must be equal |
| * to the number of {@link #srcMethods} |
| * @return types of parameters of methods having an overloaded name |
| */ |
| private Collection<TypeMirror> collectParamTypes(int[] paramsCount) { |
| Collection<TypeMirror> paramTypes = new ArrayList<TypeMirror>( |
| srcMethods.size()); |
| int index = -1; |
| for (ExecutableElement srcMethod : srcMethods) { |
| |
| index++; |
| |
| List<? extends VariableElement> params = srcMethod.getParameters(); |
| if (params.isEmpty()) { |
| paramsCount[index] = 0; |
| continue; |
| } |
| |
| final int parCount; |
| paramsCount[index] = (parCount = params.size()); |
| |
| if (parCount <= MAX_SUFFIX_TYPES) { |
| for (int i = 0; i < parCount; i++) { |
| paramTypes.add(params.get(i).asType()); |
| } |
| } |
| } |
| return !paramTypes.isEmpty() ? paramTypes |
| : Collections.<TypeMirror>emptyList(); |
| } |
| |
| /** |
| * Returns a list of methods contained directly in the given class. |
| * |
| * @param classElem class whose methods should be returned |
| * @return list of methods in the given class |
| */ |
| private static List<ExecutableElement> getExistingMethods( |
| final TypeElement classElem) { |
| List<? extends Element> elements = classElem.getEnclosedElements(); |
| if (elements.isEmpty()) { |
| return Collections.<ExecutableElement>emptyList(); |
| } |
| |
| List<ExecutableElement> methods = ElementFilter.methodsIn(elements); |
| return !methods.isEmpty() ? methods |
| : Collections.<ExecutableElement>emptyList(); |
| } |
| |
| private static String buildTestMethodName(String srcMethodName) { |
| int length = srcMethodName.length(); |
| StringBuilder buf = new StringBuilder(length + 4); |
| buf.append("test"); //NOI18N |
| buf.append(Character.toUpperCase(srcMethodName.charAt(0))); |
| if (length != 1) { |
| buf.append(srcMethodName.substring(1)); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Finds indices of test methods which have conflicting names and test |
| * no-argument source methods. |
| * |
| * @param paramsCount array containing number of parameters of each source |
| * method |
| * @param conflicting bitmap - set bits determine indices of test methods |
| * having conflicting names |
| * @return bitmap which holds information which of these conflicting test |
| * methods is a test for a no-argument source method |
| */ |
| private static BitSet findNoArgMethods(int[] paramsCount, BitSet conflicting) { |
| BitSet result = new BitSet(paramsCount.length); |
| for (int index = 0; index < paramsCount.length; index++) { |
| if ((paramsCount[index] == 0) && conflicting.get(index)) { |
| result.set(index); |
| } |
| } |
| return result; |
| } |
| |
| } |