1 package net.sourceforge.pmd.util;
2
3 import java.util.HashMap;
4 import java.util.Iterator;
5 import java.util.Map;
6
7 /**
8 * A specialized map that stores types by both their full and short (without package prefixes) names.
9 * If an incoming type shares the same name (but different package/prefix) with a type already in the
10 * map then an IllegalArgumentException will be thrown since any subsequent retrievals by said short
11 * name could be in error.
12 *
13 * @author Brian Remedios
14 */
15 public class TypeMap {
16
17 private Map<String, Class<?>> typesByName;
18
19 /**
20 * Constructor for TypeMap.
21 *
22 * @param initialSize int
23 */
24 public TypeMap(int initialSize) {
25 typesByName = new HashMap<String, Class<?>>(initialSize);
26 }
27
28 /**
29 * Constructor for TypeMap that takes in an initial set of types.
30 *
31 * @param types Class[]
32 */
33 public TypeMap(Class<?>... types) {
34 this(types.length);
35 add(types);
36 }
37
38 /**
39 * Adds a type to the receiver and stores it keyed by both its full and
40 * short names. Throws an exception if the short name of the argument
41 * matches an existing one already in the map for a different class.
42 *
43 * @param type Class
44 * @throws IllegalArgumentException
45 */
46 @SuppressWarnings("PMD.CompareObjectsWithEquals")
47 public void add(Class<?> type) {
48 final String shortName = ClassUtil.withoutPackageName(type.getName());
49 Class<?> existingType = typesByName.get(shortName);
50 if (existingType == null) {
51 typesByName.put(type.getName(), type);
52 typesByName.put(shortName, type);
53 return;
54 }
55
56 if (existingType != type) {
57 throw new IllegalArgumentException("Short name collision between existing " + existingType + " and new "
58 + type);
59 }
60 }
61
62 /**
63 * Returns whether the type is known to the receiver.
64 *
65 * @param type Class
66 * @return boolean
67 */
68 public boolean contains(Class<?> type) {
69 return typesByName.containsValue(type);
70 }
71
72 /**
73 * Returns whether the typeName is known to the receiver.
74 *
75 * @param typeName String
76 * @return boolean
77 */
78 public boolean contains(String typeName) {
79 return typesByName.containsKey(typeName);
80 }
81
82 /**
83 * Returns the type for the typeName specified.
84 *
85 * @param typeName String
86 * @return Class
87 */
88 public Class<?> typeFor(String typeName) {
89 return typesByName.get(typeName);
90 }
91
92 /**
93 * Adds an array of types to the receiver at once.
94 *
95 * @param types Class[]
96 */
97 public void add(Class<?>... types) {
98 for (Class<?> element : types) {
99 add(element);
100 }
101 }
102
103 /**
104 * Creates and returns a map of short type names (without the package
105 * prefixes) keyed by the classes themselves.
106 *
107 * @return Map
108 */
109 public Map<Class<?>, String> asInverseWithShortName() {
110 Map<Class<?>, String> inverseMap = new HashMap<Class<?>, String>(typesByName.size() / 2);
111
112 Iterator<Map.Entry<String,Class<?>>> iter = typesByName.entrySet().iterator();
113 while (iter.hasNext()) {
114 Map.Entry<String,Class<?>> entry = iter.next();
115 storeShortest(inverseMap, entry.getValue(), entry.getKey());
116 }
117
118 return inverseMap;
119 }
120
121 /**
122 * Returns the total number of entries in the receiver. This will be exactly
123 * twice the number of types added.
124 *
125 * @return the total number of entries in the receiver
126 */
127 public int size() {
128 return typesByName.size();
129 }
130
131 /**
132 * Store the shorter of the incoming value or the existing value in the map
133 * at the key specified.
134 *
135 * @param map
136 * @param key
137 * @param value
138 */
139 private void storeShortest(Map<Class<?>, String> map, Class<?> key, String value) {
140 String existingValue = map.get(key);
141
142 if (existingValue == null) {
143 map.put(key, value);
144 return;
145 }
146
147 if (existingValue.length() < value.length()) {
148 return;
149 }
150
151 map.put(key, value);
152 }
153 }