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.BitSet;
044: import java.util.Collection;
045: import java.util.HashMap;
046: import java.util.HashSet;
047: import java.util.List;
048: import java.util.Map;
049: import javax.lang.model.element.TypeElement;
050: import javax.lang.model.type.ArrayType;
051: import javax.lang.model.type.DeclaredType;
052: import javax.lang.model.type.TypeKind;
053: import javax.lang.model.type.TypeMirror;
054: import javax.lang.model.util.Elements;
055: import javax.lang.model.util.Types;
056: import static java.lang.Boolean.TRUE;
057: import static javax.lang.model.type.TypeKind.ARRAY;
058: import static javax.lang.model.type.TypeKind.DECLARED;
059:
060: /**
061: * Resolves brief names of types such that each type is assigned a unique string
062: * identifier of that type. The goal is to keep the identifier easy for
063: * understanding, short but still unique.
064: *
065: * @author Marian Petras
066: */
067: final class TypeNameIdGenerator {
068:
069: private static final String ARRAY_SUFFIX = "Arr"; //NOI18N
070:
071: private final Elements elements;
072: private final Types types;
073: /**
074: * mapping between full type names and the corresponding shorter string
075: * identifiers
076: */
077: private final Map<String, String> qualNameToId;
078:
079: /**
080: * Creates an instance of this generator with an empty
081: * {@linkplain mapping table}. It must be filled by method
082: * {@link generateMapping}.
083: *
084: * @param types an instance of a utility class {code Types}
085: */
086: private TypeNameIdGenerator(Elements elements, Types types) {
087: this .elements = elements;
088: this .types = types;
089:
090: qualNameToId = new HashMap<String, String>(20);
091: }
092:
093: /**
094: * Generates an instance of this class which will be able to translate
095: * between parameter types and the corresponding unique string identifiers.
096: * @param paramTypes parameter types that the instance must be able
097: * to translate
098: * @param types an instance of a utility class {code Types}
099: * @return instance of generator that is ready to translate any of the
100: * given type to a unique string identifier
101: * @see #getParamTypeId
102: */
103: static TypeNameIdGenerator createFor(
104: Collection<TypeMirror> paramTypes, Elements elements,
105: Types types) {
106: final TypeNameIdGenerator inst = new TypeNameIdGenerator(
107: elements, types);
108: inst.generateMapping(paramTypes);
109: return inst;
110: }
111:
112: /**
113: * Generates mapping between full type names and their shorter (but still
114: * unique) forms. The result is stored in field {@link #qualNameToId}
115: * which is then used by method {@link #getParamTypeId}.
116: *
117: * @param paramTypes parameter types for which a mapping should be
118: * generated
119: */
120: private void generateMapping(final Collection<TypeMirror> paramTypes) {
121:
122: final int typesCount = paramTypes.size();
123: int uniqueTypesCountExp = Math.max(5, typesCount / 3);
124: List<String> fullTypeNames = new ArrayList<String>(
125: uniqueTypesCountExp);
126: List<String> briefTypeNames = new ArrayList<String>(
127: uniqueTypesCountExp);
128:
129: Collection<String> processedTypes = new HashSet<String>(
130: (paramTypes.size() * 3 + 1) / 2);
131:
132: final Map<String, Object> briefTypeIdsUsage = new HashMap<String, Object>(
133: typesCount * 3 / 2);
134: final BitSet briefTypeIdsConflicting = new BitSet(typesCount);
135: final BitSet briefTypeIdsRegistered = new BitSet(typesCount);
136: int registeredTypeIdsCount = 0;
137: int conflictingTypeIdsCount = 0;
138: Collection<String> reservedTypeIds = new HashSet<String>(13);
139:
140: int index = 0;
141: for (TypeMirror type : paramTypes) {
142:
143: final TypeKind kind = type.getKind();
144: if ((kind != DECLARED) && (kind != ARRAY)) {
145: continue;
146: }
147:
148: String fullTypeName;
149: String briefTypeName;
150: if (kind == ARRAY) {
151: int arrayDim = 0;
152: StringBuilder fullTypeNameBuf = new StringBuilder(20);
153: StringBuilder briefTypeNameBuf = new StringBuilder(26);
154: TypeMirror compType = type;
155: TypeKind compTypeKind;
156: do {
157: arrayDim++;
158: fullTypeNameBuf.append('[');
159: briefTypeNameBuf.append(ARRAY_SUFFIX);
160: compType = ((ArrayType) compType)
161: .getComponentType();
162: compTypeKind = compType.getKind();
163: } while (compTypeKind == ARRAY);
164:
165: if (compTypeKind == DECLARED) {
166: String compTypeFullName = getTypeFullName(compType);
167: fullTypeName = fullTypeNameBuf.append(
168: compTypeFullName).toString();
169: if (!processedTypes.add(fullTypeName)) {
170: /* this type has been already processed */
171: continue;
172: }
173: briefTypeName = briefTypeNameBuf.insert(0,
174: getTypeBriefName(compTypeFullName))
175: .toString();
176: } else {
177: String typeId;
178: if (compTypeKind.isPrimitive()) {
179: typeId = getParamTypeId(type);
180: } else {
181: /*
182: * Might be getParamTypeId(type) as well
183: * - the result would be same. But this is faster.
184: */
185: typeId = getParamTypeId(compType); //error, none, ...
186: }
187: conflictingTypeIdsCount += registerPrimitiveTypeIdUsage(
188: typeId, briefTypeIdsUsage,
189: briefTypeIdsConflicting, reservedTypeIds);
190: assert conflictingTypeIdsCount <= registeredTypeIdsCount;
191: continue;
192: }
193: } else {
194: assert (kind == DECLARED);
195: fullTypeName = getTypeFullName(type);
196: if (!processedTypes.add(fullTypeName)) {
197: /* this type has been already processed */
198: continue;
199: }
200: briefTypeName = getTypeBriefName(fullTypeName);
201: }
202:
203: fullTypeNames.add(fullTypeName);
204: briefTypeNames.add(briefTypeName);
205:
206: briefTypeIdsRegistered.set(index);
207: registeredTypeIdsCount++;
208: conflictingTypeIdsCount += registerBriefTypeIdUsage(
209: briefTypeName, index, briefTypeIdsUsage,
210: briefTypeIdsConflicting, reservedTypeIds);
211: index++;
212: }
213: processedTypes.clear();
214: processedTypes = null;
215:
216: assert conflictingTypeIdsCount <= registeredTypeIdsCount;
217: assert conflictingTypeIdsCount == briefTypeIdsConflicting
218: .cardinality();
219: assert registeredTypeIdsCount == briefTypeIdsRegistered
220: .cardinality();
221:
222: int uniqueTypeIdsCount = registeredTypeIdsCount
223: - conflictingTypeIdsCount;
224: if (uniqueTypeIdsCount > 0) {
225:
226: /* fixate all unique brief type Id's... */
227:
228: BitSet unique = (BitSet) briefTypeIdsRegistered.clone();
229: unique.andNot(briefTypeIdsConflicting);
230:
231: for (index = unique.nextSetBit(0); //for all unique...
232: index >= 0; index = unique.nextSetBit(index + 1)) {
233: String fullTypeName = fullTypeNames.get(index);
234: String briefTypeName = briefTypeNames.get(index);
235: reservedTypeIds.add(briefTypeName); //fixate
236: qualNameToId.put(fullTypeName, briefTypeName); //add to result
237: }
238: }
239:
240: /* ... try to resolve the conflicting ones... */
241:
242: if (conflictingTypeIdsCount > 0) {
243:
244: /* ROUND #2 - try to use abbreviations of package names */
245:
246: BitSet conflicting = (BitSet) briefTypeIdsConflicting
247: .clone();
248:
249: briefTypeIdsUsage.clear();
250: briefTypeIdsRegistered.clear();
251: briefTypeIdsConflicting.clear();
252: registeredTypeIdsCount = 0;
253: conflictingTypeIdsCount = 0;
254:
255: String[] longerTypeNames = new String[typesCount];
256:
257: for (index = conflicting.nextSetBit(0); index >= 0; index = conflicting
258: .nextSetBit(index + 1)) {
259:
260: String fullTypeName = fullTypeNames.get(index);
261: String briefTypeName = briefTypeNames.get(index);
262: String longerTypeName = getLongerTypeId(fullTypeName,
263: briefTypeName);
264:
265: longerTypeNames[index] = longerTypeName;
266:
267: briefTypeIdsRegistered.set(index);
268: registeredTypeIdsCount++;
269: conflictingTypeIdsCount += registerBriefTypeIdUsage(
270: longerTypeName, index, briefTypeIdsUsage,
271: briefTypeIdsConflicting, reservedTypeIds);
272: }
273:
274: assert conflictingTypeIdsCount <= registeredTypeIdsCount;
275: assert conflictingTypeIdsCount == briefTypeIdsConflicting
276: .cardinality();
277: assert registeredTypeIdsCount == briefTypeIdsRegistered
278: .cardinality();
279:
280: uniqueTypeIdsCount = registeredTypeIdsCount
281: - conflictingTypeIdsCount;
282: if (uniqueTypeIdsCount > 0) {
283:
284: /* fixate all unique longer type Id's... */
285:
286: BitSet unique = (BitSet) briefTypeIdsRegistered.clone();
287: unique.andNot(briefTypeIdsConflicting);
288:
289: for (index = unique.nextSetBit(0); //for all unique...
290: (index >= 0) && (index < typesCount); index = unique
291: .nextSetBit(index + 1)) {
292: String fullTypeName = fullTypeNames.get(index);
293: String longerTypeId = longerTypeNames[index];
294: reservedTypeIds.add(longerTypeId); //fixate
295: qualNameToId.put(fullTypeName, longerTypeId);//add to result
296: }
297: }
298: }
299:
300: /* ... try to resolve the remaining conflicts... */
301:
302: if (conflictingTypeIdsCount > 0) {
303:
304: /* ROUND #3 - use brief names + sequential number */
305:
306: Map<String, Integer> usageNumbers = new HashMap<String, Integer>(
307: conflictingTypeIdsCount * 3 / 2);
308:
309: BitSet conflicting = briefTypeIdsConflicting;
310:
311: for (index = conflicting.nextSetBit(0); index >= 0; index = conflicting
312: .nextSetBit(index + 1)) {
313:
314: String briefTypeId = briefTypeNames.get(index);
315: Integer oldValue = usageNumbers.get(briefTypeId);
316: int suffix = (oldValue == null) ? 0 : oldValue
317: .intValue();
318: String fullTypeName = fullTypeNames.get(index);
319: String longestTypeId;
320: do {
321: suffix++;
322: longestTypeId = briefTypeId + suffix;
323: } while (reservedTypeIds.contains(longestTypeId));
324: usageNumbers.put(briefTypeId, Integer.valueOf(suffix));
325:
326: /* fixate immediately to ensure thare are really no conflicts */
327: reservedTypeIds.add(longestTypeId); //fixate
328: qualNameToId.put(fullTypeName, longestTypeId); //add to result
329: }
330: }
331:
332: /* release data that is no longer necessary: */
333: if (fullTypeNames != null) {
334: fullTypeNames = null;
335: }
336: if (briefTypeNames != null) {
337: briefTypeNames = null;
338: }
339: if (briefTypeIdsUsage != null) {
340: briefTypeIdsUsage.clear();
341: }
342: if (briefTypeIdsRegistered != null) {
343: briefTypeIdsRegistered.clear();
344: }
345: if (briefTypeIdsConflicting != null) {
346: briefTypeIdsConflicting.clear();
347: }
348: if (reservedTypeIds != null) {
349: reservedTypeIds.clear();
350: reservedTypeIds = null;
351: }
352: if (paramTypes != null) {
353: paramTypes.clear();
354: }
355: }
356:
357: /**
358: * Registers usage of a primitive type or a primitive type array.
359: *
360: * @param typeId typeId of the primitive type
361: * @param typeIdUsage registry of usages of the given type - it is used
362: * for detection of multiple usages of the same type Id
363: * @param conflictingTypesIndices bitset to which detected conflicting
364: * types should be registered
365: * @return number of newly detected types having conflicting names
366: * ({@code 0} or {@code 1})
367: */
368: private int registerPrimitiveTypeIdUsage(String typeId,
369: Map<String, Object> typeIdUsage,
370: BitSet conflictingTypesIndices,
371: Collection<String> reservedTypeIds) {
372: if (!reservedTypeIds.add(typeId)) { //this typeId is already registered
373: return 0;
374: }
375:
376: int rv = 0;
377:
378: /* check whether the type ID is overloaded: */
379: Object oldValue = typeIdUsage.get(typeId);
380: if ((oldValue != null) && (oldValue != TRUE)) {
381: assert (oldValue.getClass() == Integer.class);
382: int conflictingTypeIndex = ((Integer) oldValue).intValue();
383: assert !conflictingTypesIndices.get(conflictingTypeIndex);
384: conflictingTypesIndices.set(conflictingTypeIndex);
385: typeIdUsage.put(typeId, TRUE);
386: rv++;
387: }
388:
389: return rv;
390: }
391:
392: /**
393: * Registers usage of a given type.
394: *
395: * @param briefTypeName brief name of the type
396: * @param index index of the type in the list of types
397: * @param typeIdUsage registry of usages of the given type - it is used
398: * for detection of multiple usages of the same type Id
399: * @param conflictingTypesIndices bitset to which detected conflicting
400: * types should be registered
401: * @return number of newly detected types having conflicting names
402: * ({@code 0}, {@code 1} or {@code 2})
403: */
404: private int registerBriefTypeIdUsage(String typeId, int index,
405: Map<String, Object> typeIdUsage,
406: BitSet conflictingTypesIndices,
407: Collection<String> reservedTypeIds) {
408: Object oldValue = typeIdUsage.put(typeId, Integer
409: .valueOf(index));
410: boolean nameConflict = (oldValue != null)
411: || (reservedTypeIds != null)
412: && (reservedTypeIds.contains(typeId));
413:
414: assert !conflictingTypesIndices.get(index);
415:
416: int rv = 0;
417: if (nameConflict) {
418: if ((oldValue != null) && (oldValue != TRUE)) {
419: /*
420: * (oldValue == Integer) ... conflict with another brief Id
421: * detected
422: * (oldValue == null) ...... conflict with a reserved type Id
423: * detected
424: * (oldValue == TRUE) ...... name has been already known to be
425: * in conflict with some other type
426: */
427: assert (oldValue.getClass() == Integer.class);
428: int conflictingTypeIndex = ((Integer) oldValue)
429: .intValue();
430: assert !conflictingTypesIndices
431: .get(conflictingTypeIndex);
432: conflictingTypesIndices.set(conflictingTypeIndex);
433: rv++;
434: }
435: conflictingTypesIndices.set(index);
436: typeIdUsage.put(typeId, TRUE);
437: rv++;
438: }
439: return rv;
440: }
441:
442: /**
443: * Returns a short but unique parameter type identifier for the given type.
444: *
445: * @param type type for which a unique id is requested
446: * @return unique type id for the given type
447: */
448: String getParamTypeId(TypeMirror type) {
449: if (type == null) {
450: throw new IllegalArgumentException("null"); //NOI18N
451: }
452:
453: final TypeKind kind = type.getKind();
454: if ((kind != DECLARED) && (kind != ARRAY)) {
455:
456: if (kind.isPrimitive()) {
457: return type.toString();
458: }
459:
460: switch (kind) {
461: case ERROR:
462: return "ErrorType"; //NOI18N
463: case NONE:
464: assert false;
465: return "NoType"; //NOI18N
466: case VOID:
467: assert false;
468: return "VoidType"; //NOI18N
469: case NULL:
470: assert false;
471: return "NullType"; //NOI18N
472: case EXECUTABLE:
473: case PACKAGE:
474: case TYPEVAR:
475: case WILDCARD:
476: assert false;
477: return null;
478: default: // including OTHER
479: return "UnknownType"; //NOI18N
480: }
481: }
482:
483: String fullTypeName;
484: if (kind == ARRAY) {
485: int arrayDim = 0;
486: StringBuilder fullTypeNameBuf = new StringBuilder(20);
487: TypeMirror compType = type;
488: TypeKind compTypeKind;
489: do {
490: arrayDim++;
491: fullTypeNameBuf.append('[');
492: compType = ((ArrayType) compType).getComponentType();
493: compTypeKind = compType.getKind();
494: } while (compTypeKind == ARRAY);
495:
496: if (compTypeKind == DECLARED) {
497: fullTypeName = fullTypeNameBuf.append(
498: getTypeFullName(compType)).toString();
499: } else if (compTypeKind.isPrimitive()) {
500: StringBuilder paramTypeIdBuf = new StringBuilder(17);
501: paramTypeIdBuf.append(compType.toString());
502: for (int i = 0; i < arrayDim; i++) {
503: paramTypeIdBuf.append(ARRAY_SUFFIX);
504: }
505: return paramTypeIdBuf.toString();
506: } else {
507: return getParamTypeId(compType); //error, none, void, ...
508: }
509: } else {
510: assert (kind == DECLARED);
511: fullTypeName = getTypeFullName(type);
512: }
513:
514: String id = qualNameToId.get(fullTypeName);
515: if (id == null) {
516: throw new IllegalArgumentException("unknown type"); //NOI18N
517: }
518: return id;
519: }
520:
521: /**
522: * Generates a string describing the given type. If the type is
523: * parameterized, its erasure is used instead.
524: *
525: * @param type type for which a string identifier is to be generated
526: * @return generated string identifier for the given type
527: */
528: private String getTypeFullName(TypeMirror type) {
529: assert type.getKind() == DECLARED;
530: DeclaredType typeErasure = (DeclaredType) types.erasure(type);
531: TypeElement typeErasureElem = (TypeElement) typeErasure
532: .asElement();
533: return elements.getBinaryName(typeErasureElem).toString();
534: }
535:
536: /**
537: * Returns a brief version of a given type name.
538: *
539: * @param typeFullName full type name
540: * @return brief version of the type, i.e. package name is stripped
541: */
542: private static String getTypeBriefName(String typeFullName) {
543: int dotIndex = typeFullName.lastIndexOf('.');
544: String result = (dotIndex == -1) ? typeFullName : typeFullName
545: .substring(dotIndex + 1);
546: int dollarIndex = result.lastIndexOf('$');
547: if (dollarIndex != -1) { //nested class
548: StringBuilder buf = new StringBuilder(result);
549: do {
550: buf.deleteCharAt(dollarIndex);
551: dollarIndex = result.lastIndexOf('$', dollarIndex - 1);
552: } while (dollarIndex != -1);
553: result = buf.toString();
554: }
555: return result;
556: }
557:
558: /**
559: * Returns a longer type Id of the given type.
560: * The longer type Id is made of brief type Id by prepending an abbreviation
561: * of the type's package name.
562: * The package name abbreviation is a sequence of first characters of parts
563: * of the package name. An abbreviation of a type that has an empty package
564: * name (i.e. belonging to the default package), is an empty string.
565: * <p>
566: * Example:<br />
567: * <code>getLongerTypeId(...)</code>
568: * for type <code><u>j</u>ava.<u>l</u>ang.<u>String</u></code>
569: * is <code>"jlString"</code>.
570: * </p>
571: *
572: * @param typeFullName full type Id of the type whose longer type Id
573: * is to be returned
574: * @param briefTypeId brief type Id of the type
575: * @return longer type Id of the given type;
576: * if the given type belongs to the default package
577: * (i.e. the package name is empty), the same (instance of) string
578: * is returned
579: */
580: private static String getLongerTypeId(String typeFullName,
581: String briefTypeId) {
582: if (typeFullName.charAt(0) == '[') { //it's an array type
583: int startIndex = 0;
584: do {
585: startIndex++;
586: } while (typeFullName.charAt(startIndex) == '[');
587: typeFullName = typeFullName.substring(startIndex);
588: }
589:
590: int lastDot = typeFullName.lastIndexOf('.');
591: if (lastDot == -1) {
592: return briefTypeId;
593: }
594:
595: String pkgName = typeFullName.substring(0, lastDot);
596: StringBuilder buf = new StringBuilder(10);
597: int nextDot = -1;
598: do {
599: int pkgPartStart = nextDot + 1;
600: buf.append(pkgName.charAt(pkgPartStart));
601: nextDot = pkgName.indexOf('.', pkgPartStart);
602: assert (nextDot != pkgPartStart);
603: } while (nextDot != -1);
604: return buf.append(briefTypeId).toString();
605: }
606:
607: }
|