001 package net.sourceforge.pmd.util;
002
003 import java.util.HashMap;
004 import java.util.Iterator;
005 import java.util.Map;
006
007 /**
008 * A specialized map that stores types by both their full and short (without package prefixes) names.
009 * If an incoming type shares the same name (but different package/prefix) with a type already in the
010 * map then an IllegalArgumentException will be thrown since any subsequent retrievals by said short
011 * name could be in error.
012 *
013 * @author Brian Remedios
014 */
015 public class TypeMap {
016
017 private Map<String, Class<?>> typesByName;
018
019 /**
020 * Constructor for TypeMap.
021 *
022 * @param initialSize int
023 */
024 public TypeMap(int initialSize) {
025 typesByName = new HashMap<String, Class<?>>(initialSize);
026 }
027
028 /**
029 * Constructor for TypeMap that takes in an initial set of types.
030 *
031 * @param types Class[]
032 */
033 public TypeMap(Class<?>... types) {
034 this(types.length);
035 add(types);
036 }
037
038 /**
039 * Adds a type to the receiver and stores it keyed by both its full and
040 * short names. Throws an exception if the short name of the argument
041 * matches an existing one already in the map for a different class.
042 *
043 * @param type Class
044 * @throws IllegalArgumentException
045 */
046 @SuppressWarnings("PMD.CompareObjectsWithEquals")
047 public void add(Class<?> type) {
048 final String shortName = ClassUtil.withoutPackageName(type.getName());
049 Class<?> existingType = typesByName.get(shortName);
050 if (existingType == null) {
051 typesByName.put(type.getName(), type);
052 typesByName.put(shortName, type);
053 return;
054 }
055
056 if (existingType != type) {
057 throw new IllegalArgumentException("Short name collision between existing " + existingType + " and new "
058 + type);
059 }
060 }
061
062 /**
063 * Returns whether the type is known to the receiver.
064 *
065 * @param type Class
066 * @return boolean
067 */
068 public boolean contains(Class<?> type) {
069 return typesByName.containsValue(type);
070 }
071
072 /**
073 * Returns whether the typeName is known to the receiver.
074 *
075 * @param typeName String
076 * @return boolean
077 */
078 public boolean contains(String typeName) {
079 return typesByName.containsKey(typeName);
080 }
081
082 /**
083 * Returns the type for the typeName specified.
084 *
085 * @param typeName String
086 * @return Class
087 */
088 public Class<?> typeFor(String typeName) {
089 return typesByName.get(typeName);
090 }
091
092 /**
093 * Adds an array of types to the receiver at once.
094 *
095 * @param types Class[]
096 */
097 public void add(Class<?>... types) {
098 for (Class<?> element : types) {
099 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 iter = typesByName.entrySet().iterator();
113 while (iter.hasNext()) {
114 Map.Entry entry = (Map.Entry) iter.next();
115 storeShortest(inverseMap, entry.getValue(), (String) 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 map, Object key, String value) {
140 String existingValue = (String) 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 }
|