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