View Javadoc

1   package org.springframework.roo.model;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.Collections;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  import java.util.TreeMap;
12  import java.util.Vector;
13  
14  import org.springframework.roo.support.util.Assert;
15  import org.springframework.roo.support.util.StringUtils;
16  
17  /**
18   * Immutable representation of a Java type.
19   * 
20   * <p>
21   * Note that a Java type can be contained within a package, but a package is not a type.
22   * 
23   * <p>
24   * This class is used whenever a formal reference to a Java type is required.
25   * It provides convenient ways to determine the type's simple name and package name.
26   * A related {@link Converter} is also offered.
27   * 
28   * @author Ben Alex
29   * @since 1.0
30   *
31   */
32  public final class JavaType implements Comparable<JavaType>, Cloneable {
33  	private List<JavaType> parameters = new ArrayList<JavaType>();
34  	private JavaSymbolName argName = null;
35  	private int array = 0;
36  	private boolean defaultPackage;
37  	private DataType dataType;
38  	private String fullyQualifiedTypeName;
39  	private String simpleTypeName;
40  	public static final JavaType BOOLEAN_OBJECT = new JavaType("java.lang.Boolean", 0, DataType.TYPE, null, null);
41  	public static final JavaType CHAR_OBJECT = new JavaType("java.lang.Character", 0, DataType.TYPE, null, null);
42  	public static final JavaType STRING_OBJECT = new JavaType("java.lang.String", 0, DataType.TYPE, null, null);
43  	public static final JavaType BYTE_OBJECT = new JavaType("java.lang.Byte", 0, DataType.TYPE, null, null);
44  	public static final JavaType SHORT_OBJECT = new JavaType("java.lang.Short", 0, DataType.TYPE, null, null);
45  	public static final JavaType INT_OBJECT = new JavaType("java.lang.Integer", 0, DataType.TYPE, null, null);
46  	public static final JavaType LONG_OBJECT = new JavaType("java.lang.Long", 0, DataType.TYPE, null, null);
47  	public static final JavaType FLOAT_OBJECT = new JavaType("java.lang.Float", 0, DataType.TYPE, null, null);
48  	public static final JavaType DOUBLE_OBJECT = new JavaType("java.lang.Double", 0, DataType.TYPE, null, null);
49  	public static final JavaType VOID_OBJECT = new JavaType("java.lang.Void", 0, DataType.TYPE, null, null);
50  	public static final JavaType BOOLEAN_PRIMITIVE = new JavaType("java.lang.Boolean", 0, DataType.PRIMITIVE, null, null);
51  	public static final JavaType CHAR_PRIMITIVE = new JavaType("java.lang.Character", 0, DataType.PRIMITIVE, null, null);
52  	public static final JavaType BYTE_PRIMITIVE = new JavaType("java.lang.Byte", 0, DataType.PRIMITIVE, null, null);
53  	public static final JavaType SHORT_PRIMITIVE = new JavaType("java.lang.Short", 0, DataType.PRIMITIVE, null, null);
54  	public static final JavaType INT_PRIMITIVE = new JavaType("java.lang.Integer", 0, DataType.PRIMITIVE, null, null);
55  	public static final JavaType LONG_PRIMITIVE = new JavaType("java.lang.Long", 0, DataType.PRIMITIVE, null, null);
56  	public static final JavaType FLOAT_PRIMITIVE = new JavaType("java.lang.Float", 0, DataType.PRIMITIVE, null, null);
57  	public static final JavaType DOUBLE_PRIMITIVE = new JavaType("java.lang.Double", 0, DataType.PRIMITIVE, null, null);
58  	public static final JavaType VOID_PRIMITIVE = new JavaType("java.lang.Void", 0, DataType.PRIMITIVE, null, null);
59  
60  	private static final Set<String> commonCollectionTypes = new HashSet<String>();
61  
62  	static {
63  		commonCollectionTypes.add(Collection.class.getName());
64  		commonCollectionTypes.add(List.class.getName());
65  		commonCollectionTypes.add(Set.class.getName());
66  		commonCollectionTypes.add(Map.class.getName());
67  		commonCollectionTypes.add(HashMap.class.getName());
68  		commonCollectionTypes.add(TreeMap.class.getName());
69  		commonCollectionTypes.add(ArrayList.class.getName());
70  		commonCollectionTypes.add(Vector.class.getName());
71  		commonCollectionTypes.add(HashSet.class.getName());
72  	}
73  
74  	/**
75  	 * Construct a JavaType.
76  	 * 
77  	 * <p>
78  	 * The fully qualified type name will be enforced as follows:
79  	 * 
80  	 * <ul>
81  	 * <li>The rules listed in {link {@link JavaTypeUtils#assertJavaNameLegal(String)}}
82  	 * <li>First letter of simple type name must be uppercase</li>
83  	 * </ul>
84  	 * 
85  	 * <p>
86  	 * A fully qualified type name may include or exclude a package designator.
87  	 * 
88  	 * @param fullyQualifiedTypeName the name (as per the above rules; mandatory)
89  	 */
90  	public JavaType(String fullyQualifiedTypeName) {
91  		this(fullyQualifiedTypeName, 0, DataType.TYPE, null, null);
92  	}
93  
94  	/**
95  	 * Construct a {@link JavaType} with full details. Recall that {@link JavaType} is immutable and therefore this is the only way of
96  	 * setting these non-default values.
97  	 * 
98  	 * @param fullyQualifiedTypeName the name (as per the rules above)
99  	 * @param array number of array indicies (0 = not an array, 1 = single dimensional array etc)
100 	 * @param primitive whether this type is representing the equivalent primitive
101 	 * @param argName the type argument name to this particular Java type (can be null if unassigned)
102 	 * @param parameters the type parameters applicable (can be null if there aren't any)
103 	 */
104 	public JavaType(String fullyQualifiedTypeName, int array, DataType primitive, JavaSymbolName argName, List<JavaType> parameters) {
105 		if (fullyQualifiedTypeName == null || fullyQualifiedTypeName.length() == 0) {
106 			throw new IllegalArgumentException("Fully qualified type name required");
107 		}
108 		JavaSymbolName.assertJavaNameLegal(fullyQualifiedTypeName);
109 		this.fullyQualifiedTypeName = fullyQualifiedTypeName;
110 		this.defaultPackage = !fullyQualifiedTypeName.contains(".");
111 		if (defaultPackage) {
112 			simpleTypeName = fullyQualifiedTypeName;
113 		} else {
114 			int offset = fullyQualifiedTypeName.lastIndexOf(".");
115 			simpleTypeName = fullyQualifiedTypeName.substring(offset+1);
116 		}
117 		if (!Character.isUpperCase(simpleTypeName.charAt(0))) {
118 			// Doesn't start with an uppercase letter, so let's verify it starts with an underscore and then an uppercase
119 			if (simpleTypeName.length() < 2 || !Character.isUpperCase(simpleTypeName.charAt(1)) || '_' != simpleTypeName.charAt(0)) {
120 				throw new IllegalArgumentException("The first letter of the type name portion must be uppercase (attempted '" + fullyQualifiedTypeName + "')");
121 			}
122 		}
123 		
124 		this.array = array;
125 		this.dataType = primitive;
126 		if (parameters != null) {
127 			this.parameters = parameters;
128 		}
129 		this.argName = argName;
130 	}
131 
132 	/**
133 	 * @return the name (does not contain any periods; never null or empty)
134 	 */
135 	public String getSimpleTypeName() {
136 		return simpleTypeName;
137 	}
138 
139 	/**
140 	 * @return the fully qualified name (complies with the rules specified in the constructor)
141 	 */
142 	public String getFullyQualifiedTypeName() {
143 		return fullyQualifiedTypeName;
144 	}
145 
146 	// used for wildcard type parameters; it must be one or the other
147 	public static final JavaSymbolName WILDCARD_EXTENDS = new JavaSymbolName("_ROO_WILDCARD_EXTENDS_");  // List<? extends YY>
148 	public static final JavaSymbolName WILDCARD_SUPER = new JavaSymbolName("_ROO_WILDCARD_SUPER_");      // List<? super XXXX>
149 	public static final JavaSymbolName WILDCARD_NEITHER = new JavaSymbolName("_ROO_WILDCARD_NEITHER_");  // List<?>
150 
151 	/**
152 	 * Obtains the name of this type, including type parameters. It will be formatted in a manner compatible with non-static use.
153 	 * No type name import resolution will take place. This is a side-effect free method.
154 	 * 
155 	 * @return the type name, including parameters, as legal Java code (never null or empty)
156 	 */
157 	public String getNameIncludingTypeParameters() {
158 		return getNameIncludingTypeParameters(false, null, new HashMap<String, String>());
159 	}
160 
161 	/**
162 	 * Obtains the name of this type, including type parameters. It will be formatted in a manner compatible with either static
163 	 * or non-static usage, as per the passed argument. Type names will attempt to be resolved (and automatically registered)
164 	 * using the passed resolver. This method will have side-effects on the passed resolver.
165 	 * 
166 	 * @param staticForm true if the output should be compatible with static use
167 	 * @param resolver the resolver to use (may be null in which case no import resolution will occur)
168 	 * @return the type name, including parameters, as legal Java code (never null or empty)
169 	 */
170 	public String getNameIncludingTypeParameters(boolean staticForm, ImportRegistrationResolver resolver) {
171 		return getNameIncludingTypeParameters(staticForm, resolver, new HashMap<String, String>());
172 	}
173 	
174 	private String getNameIncludingTypeParameters(boolean staticForm, ImportRegistrationResolver resolver, Map<String, String> types) {
175 		if (DataType.PRIMITIVE == dataType) {
176 			Assert.isTrue(parameters.size() == 0, "A primitive cannot have parameters");
177 			if (this.fullyQualifiedTypeName.equals(Integer.class.getName())) {
178 				return "int" + getArraySuffix();
179 			} else if (this.fullyQualifiedTypeName.equals(Character.class.getName())) {
180 					return "char" + getArraySuffix();
181 			} else if (this.fullyQualifiedTypeName.equals(Void.class.getName())) {
182 				return "void";
183 			}
184 			return StringUtils.uncapitalize(this.getSimpleTypeName() + getArraySuffix());
185 		}
186 		
187 		StringBuilder sb = new StringBuilder();
188 		
189 		if (WILDCARD_EXTENDS.equals(argName)) {
190 			sb.append("?");
191 			if (dataType == DataType.TYPE || !staticForm) {
192 				sb.append(" extends ");
193 			} else if (types.containsKey(fullyQualifiedTypeName)) {
194 				sb.append(" extends ").append(types.get(fullyQualifiedTypeName));
195 			}
196 		} else if (WILDCARD_SUPER.equals(argName)) {
197 			sb.append("?");
198 			if (dataType == DataType.TYPE || !staticForm) {
199 				sb.append(" super ");
200 			} else if (types.containsKey(fullyQualifiedTypeName)) {
201 				sb.append(" extends ").append(types.get(fullyQualifiedTypeName));
202 			}
203 		} else if (WILDCARD_NEITHER.equals(argName)) {
204 			sb.append("?");
205 		} else if (argName != null && !staticForm) {
206 			sb.append(argName);
207 			if (dataType == DataType.TYPE) {
208 				sb.append(" extends ");
209 			}
210 		}
211 		
212 		if (!WILDCARD_NEITHER.equals(argName)) {
213 			// It wasn't a WILDCARD_NEITHER, so we might need to continue with more details
214 			
215 			if (dataType == DataType.TYPE || !staticForm) {
216 				// TODO: Use the import registration resolver
217 				if (resolver != null) {
218 					if (resolver.isFullyQualifiedFormRequiredAfterAutoImport(this)) {
219 						sb.append(fullyQualifiedTypeName);
220 					} else {
221 						sb.append(getSimpleTypeName());
222 					}
223 				} else {
224 					sb.append(fullyQualifiedTypeName);
225 				}
226 			}
227 			
228 			if (this.parameters.size() > 0 && (dataType == DataType.TYPE || !staticForm)) {
229 				sb.append("<");
230 				int counter = 0;
231 				for (JavaType param : this.parameters) {
232 					counter++;
233 					if (counter > 1) {
234 						sb.append(", ");
235 					}
236 					sb.append(param.getNameIncludingTypeParameters(staticForm, resolver, types));
237 					counter++;
238 				}
239 				sb.append(">");
240 			}
241 			
242 			sb.append(getArraySuffix());
243 		}
244 
245 		if (argName != null && !argName.equals(WILDCARD_EXTENDS) && !argName.equals(WILDCARD_SUPER) && !argName.equals(WILDCARD_NEITHER)) {
246 			types.put(this.argName.getSymbolName(), sb.toString());
247 		}
248 		
249 		return sb.toString();
250 	}
251 	
252 	/**
253 	 * @return the package name (never null)
254 	 */
255 	public JavaPackage getPackage() {
256 		if (isDefaultPackage()) {
257 			return new JavaPackage("");
258 		}
259 		
260 		JavaType enclosingType = getEnclosingType();
261 		if (enclosingType != null) {
262 			return enclosingType.getPackage();
263 		}
264 
265 		int offset = fullyQualifiedTypeName.lastIndexOf(".");
266 		return new JavaPackage(fullyQualifiedTypeName.substring(0, offset));
267 	}
268 	
269 	/**
270 	 * @return the enclosing type, if any (will return null if there is no enclosing type)
271 	 */
272 	public JavaType getEnclosingType() {
273 		int offset = fullyQualifiedTypeName.lastIndexOf(".");
274 		if (offset == -1) {
275 			// there is no dot in the name, so there's no way there's an enclosing type
276 			return null;
277 		}
278 		String possibleName = fullyQualifiedTypeName.substring(0, offset);
279 		int offset2 = possibleName.lastIndexOf(".");
280 		
281 		// start by handling if the type name is Foo.Bar (ie an enclosed type within the default package)
282 		String enclosedWithinPackage = null;
283 		String enclosedWithinTypeName = possibleName;
284 
285 		// handle the probability the type name is within a package like com.alpha.Foo.Bar
286 		if (offset2 > -1) {
287 			enclosedWithinPackage = possibleName.substring(0, offset2);
288 			enclosedWithinTypeName = possibleName.substring(offset2+1);
289 		}
290 		if (enclosedWithinTypeName.charAt(0) == enclosedWithinTypeName.toUpperCase().charAt(0)) {
291 			// first letter is uppercase, so treat it as a type name
292 			String preTypeNamePortion = enclosedWithinPackage == null ? "" : (enclosedWithinPackage + ".");
293 			return new JavaType(preTypeNamePortion + enclosedWithinTypeName);
294 		}
295 		return null;
296 	}
297 	
298 	public boolean isDefaultPackage() {
299 		return defaultPackage;
300 	}
301 	
302 	public final int hashCode() {
303 		return this.fullyQualifiedTypeName.hashCode();
304 	}
305 
306 	public boolean isPrimitive() {
307 		return DataType.PRIMITIVE == dataType;
308 	}
309 
310 	public boolean isCommonCollectionType() {
311 		return commonCollectionTypes.contains(this.fullyQualifiedTypeName);
312 	}
313 
314 	public List<JavaType> getParameters() {
315 		return Collections.unmodifiableList(this.parameters);
316 	}
317 	
318 	public boolean isArray() {
319 		return array > 0;
320 	}
321 	
322 	public int getArray() {
323 		return array;
324 	}
325 
326 	private String getArraySuffix() {
327 		if (array == 0) {
328 			return "";
329 		}
330 		StringBuilder sb = new StringBuilder();
331 		for (int i = 0; i < array; i++) {
332 			sb.append("[]");
333 		}
334 		return sb.toString();
335 	}
336 
337 	public final boolean equals(Object obj) {
338 		// NB: Not using the normal convention of delegating to compareTo (for efficiency reasons)
339 		return obj != null && obj instanceof JavaType && this.fullyQualifiedTypeName.equals(((JavaType)obj).fullyQualifiedTypeName) && this.dataType == ((JavaType)obj).dataType;
340 	}
341 
342 	public final int compareTo(JavaType o) {
343 		// NB: If adding more fields to this class ensure the equals(Object) method is updated accordingly 
344 		if (o == null) return -1;
345 		return this.fullyQualifiedTypeName.compareTo(o.fullyQualifiedTypeName);
346 	}
347 	
348 	public final String toString() {
349 		return getNameIncludingTypeParameters();
350 	}
351 
352 	public JavaSymbolName getArgName() {
353 		return argName;
354 	}
355 
356 	public DataType getDataType() {
357 		return dataType;
358 	}
359 
360 	
361 // Shouldn't be required given JavaType is immutable!
362 //	@Override
363 //	public JavaType clone() throws CloneNotSupportedException {
364 //		return new JavaType(this.fullyQualifiedTypeName, this.array, this.primitive);
365 //	}
366 	
367 }