View Javadoc
1   /**
2    *    Copyright 2006-2016 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.generator.api.dom.java;
17  
18  import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
19  import static org.mybatis.generator.internal.util.messages.Messages.getString;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.StringTokenizer;
24  
25  /**
26   * The Class FullyQualifiedJavaType.
27   *
28   * @author Jeff Butler
29   */
30  public class FullyQualifiedJavaType implements
31          Comparable<FullyQualifiedJavaType> {
32      
33      /** The Constant JAVA_LANG. */
34      private static final String JAVA_LANG = "java.lang"; //$NON-NLS-1$
35      
36      /** The int instance. */
37      private static FullyQualifiedJavaType intInstance = null;
38      
39      /** The string instance. */
40      private static FullyQualifiedJavaType stringInstance = null;
41      
42      /** The boolean primitive instance. */
43      private static FullyQualifiedJavaType booleanPrimitiveInstance = null;
44      
45      /** The object instance. */
46      private static FullyQualifiedJavaType objectInstance = null;
47      
48      /** The date instance. */
49      private static FullyQualifiedJavaType dateInstance = null;
50      
51      /** The criteria instance. */
52      private static FullyQualifiedJavaType criteriaInstance = null;
53      
54      /** The generated criteria instance. */
55      private static FullyQualifiedJavaType generatedCriteriaInstance = null;
56  
57      /** The short name without any generic arguments. */
58      private String baseShortName;
59  
60      /** The fully qualified name without any generic arguments. */
61      private String baseQualifiedName;
62  
63      /** The explicitly imported. */
64      private boolean explicitlyImported;
65      
66      /** The package name. */
67      private String packageName;
68      
69      /** The primitive. */
70      private boolean primitive;
71      
72      /** The is array. */
73      private boolean isArray;
74      
75      /** The primitive type wrapper. */
76      private PrimitiveTypeWrapper primitiveTypeWrapper;
77      
78      /** The type arguments. */
79      private List<FullyQualifiedJavaType> typeArguments;
80  
81      // the following three values are used for dealing with wildcard types
82      /** The wildcard type. */
83      private boolean wildcardType;
84      
85      /** The bounded wildcard. */
86      private boolean boundedWildcard;
87      
88      /** The extends bounded wildcard. */
89      private boolean extendsBoundedWildcard;
90  
91      /**
92       * Use this constructor to construct a generic type with the specified type parameters.
93       *
94       * @param fullTypeSpecification
95       *            the full type specification
96       */
97      public FullyQualifiedJavaType(String fullTypeSpecification) {
98          super();
99          typeArguments = new ArrayList<FullyQualifiedJavaType>();
100         parse(fullTypeSpecification);
101     }
102 
103     /**
104      * Checks if is explicitly imported.
105      *
106      * @return Returns the explicitlyImported.
107      */
108     public boolean isExplicitlyImported() {
109         return explicitlyImported;
110     }
111 
112     /**
113      * This method returns the fully qualified name - including any generic type parameters.
114      *
115      * @return Returns the fullyQualifiedName.
116      */
117     public String getFullyQualifiedName() {
118         StringBuilder sb = new StringBuilder();
119         if (wildcardType) {
120             sb.append('?');
121             if (boundedWildcard) {
122                 if (extendsBoundedWildcard) {
123                     sb.append(" extends "); //$NON-NLS-1$
124                 } else {
125                     sb.append(" super "); //$NON-NLS-1$
126                 }
127 
128                 sb.append(baseQualifiedName);
129             }
130         } else {
131             sb.append(baseQualifiedName);
132         }
133 
134         if (typeArguments.size() > 0) {
135             boolean first = true;
136             sb.append('<');
137             for (FullyQualifiedJavaType fqjt : typeArguments) {
138                 if (first) {
139                     first = false;
140                 } else {
141                     sb.append(", "); //$NON-NLS-1$
142                 }
143                 sb.append(fqjt.getFullyQualifiedName());
144 
145             }
146             sb.append('>');
147         }
148 
149         return sb.toString();
150     }
151 
152     public String getFullyQualifiedNameWithoutTypeParameters() {
153         return baseQualifiedName;
154     }
155     
156     /**
157      * Returns a list of Strings that are the fully qualified names of this type, and any generic type argument
158      * associated with this type.
159      *
160      * @return the import list
161      */
162     public List<String> getImportList() {
163         List<String> answer = new ArrayList<String>();
164         if (isExplicitlyImported()) {
165             int index = baseShortName.indexOf('.');
166             if (index == -1) {
167                 answer.add(baseQualifiedName);
168             } else {
169                 // an inner class is specified, only import the top
170                 // level class
171                 StringBuilder sb = new StringBuilder();
172                 sb.append(packageName);
173                 sb.append('.');
174                 sb.append(baseShortName.substring(0, index));
175                 answer.add(sb.toString());
176             }
177         }
178 
179         for (FullyQualifiedJavaType fqjt : typeArguments) {
180             answer.addAll(fqjt.getImportList());
181         }
182 
183         return answer;
184     }
185 
186     /**
187      * Gets the package name.
188      *
189      * @return Returns the packageName.
190      */
191     public String getPackageName() {
192         return packageName;
193     }
194 
195     /**
196      * Gets the short name.
197      *
198      * @return Returns the shortName - including any type arguments.
199      */
200     public String getShortName() {
201         StringBuilder sb = new StringBuilder();
202         if (wildcardType) {
203             sb.append('?');
204             if (boundedWildcard) {
205                 if (extendsBoundedWildcard) {
206                     sb.append(" extends "); //$NON-NLS-1$
207                 } else {
208                     sb.append(" super "); //$NON-NLS-1$
209                 }
210 
211                 sb.append(baseShortName);
212             }
213         } else {
214             sb.append(baseShortName);
215         }
216 
217         if (typeArguments.size() > 0) {
218             boolean first = true;
219             sb.append('<');
220             for (FullyQualifiedJavaType fqjt : typeArguments) {
221                 if (first) {
222                     first = false;
223                 } else {
224                     sb.append(", "); //$NON-NLS-1$
225                 }
226                 sb.append(fqjt.getShortName());
227 
228             }
229             sb.append('>');
230         }
231 
232         return sb.toString();
233     }
234     
235     public String getShortNameWithoutTypeArguments() {
236         return baseShortName;
237     }
238 
239     /*
240      * (non-Javadoc)
241      * 
242      * @see java.lang.Object#equals(java.lang.Object)
243      */
244     @Override
245     public boolean equals(Object obj) {
246         if (this == obj) {
247             return true;
248         }
249 
250         if (!(obj instanceof FullyQualifiedJavaType)) {
251             return false;
252         }
253 
254         FullyQualifiedJavaType other = (FullyQualifiedJavaType) obj;
255 
256         return getFullyQualifiedName().equals(other.getFullyQualifiedName());
257     }
258 
259     /*
260      * (non-Javadoc)
261      * 
262      * @see java.lang.Object#hashCode()
263      */
264     @Override
265     public int hashCode() {
266         return getFullyQualifiedName().hashCode();
267     }
268 
269     /*
270      * (non-Javadoc)
271      * 
272      * @see java.lang.Object#toString()
273      */
274     @Override
275     public String toString() {
276         return getFullyQualifiedName();
277     }
278 
279     /**
280      * Checks if is primitive.
281      *
282      * @return Returns the primitive.
283      */
284     public boolean isPrimitive() {
285         return primitive;
286     }
287 
288     /**
289      * Gets the primitive type wrapper.
290      *
291      * @return Returns the wrapperClass.
292      */
293     public PrimitiveTypeWrapper getPrimitiveTypeWrapper() {
294         return primitiveTypeWrapper;
295     }
296 
297     /**
298      * Gets the int instance.
299      *
300      * @return the int instance
301      */
302     public static final FullyQualifiedJavaType getIntInstance() {
303         if (intInstance == null) {
304             intInstance = new FullyQualifiedJavaType("int"); //$NON-NLS-1$
305         }
306 
307         return intInstance;
308     }
309 
310     /**
311      * Gets the new map instance.
312      *
313      * @return the new map instance
314      */
315     public static final FullyQualifiedJavaType getNewMapInstance() {
316         // always return a new instance because the type may be parameterized
317         return new FullyQualifiedJavaType("java.util.Map"); //$NON-NLS-1$
318     }
319 
320     /**
321      * Gets the new list instance.
322      *
323      * @return the new list instance
324      */
325     public static final FullyQualifiedJavaType getNewListInstance() {
326         // always return a new instance because the type may be parameterized
327         return new FullyQualifiedJavaType("java.util.List"); //$NON-NLS-1$
328     }
329 
330     /**
331      * Gets the new hash map instance.
332      *
333      * @return the new hash map instance
334      */
335     public static final FullyQualifiedJavaType getNewHashMapInstance() {
336         // always return a new instance because the type may be parameterized
337         return new FullyQualifiedJavaType("java.util.HashMap"); //$NON-NLS-1$
338     }
339 
340     /**
341      * Gets the new array list instance.
342      *
343      * @return the new array list instance
344      */
345     public static final FullyQualifiedJavaType getNewArrayListInstance() {
346         // always return a new instance because the type may be parameterized
347         return new FullyQualifiedJavaType("java.util.ArrayList"); //$NON-NLS-1$
348     }
349 
350     /**
351      * Gets the new iterator instance.
352      *
353      * @return the new iterator instance
354      */
355     public static final FullyQualifiedJavaType getNewIteratorInstance() {
356         // always return a new instance because the type may be parameterized
357         return new FullyQualifiedJavaType("java.util.Iterator"); //$NON-NLS-1$
358     }
359 
360     /**
361      * Gets the string instance.
362      *
363      * @return the string instance
364      */
365     public static final FullyQualifiedJavaType getStringInstance() {
366         if (stringInstance == null) {
367             stringInstance = new FullyQualifiedJavaType("java.lang.String"); //$NON-NLS-1$
368         }
369 
370         return stringInstance;
371     }
372 
373     /**
374      * Gets the boolean primitive instance.
375      *
376      * @return the boolean primitive instance
377      */
378     public static final FullyQualifiedJavaType getBooleanPrimitiveInstance() {
379         if (booleanPrimitiveInstance == null) {
380             booleanPrimitiveInstance = new FullyQualifiedJavaType("boolean"); //$NON-NLS-1$
381         }
382 
383         return booleanPrimitiveInstance;
384     }
385 
386     /**
387      * Gets the object instance.
388      *
389      * @return the object instance
390      */
391     public static final FullyQualifiedJavaType getObjectInstance() {
392         if (objectInstance == null) {
393             objectInstance = new FullyQualifiedJavaType("java.lang.Object"); //$NON-NLS-1$
394         }
395 
396         return objectInstance;
397     }
398 
399     /**
400      * Gets the date instance.
401      *
402      * @return the date instance
403      */
404     public static final FullyQualifiedJavaType getDateInstance() {
405         if (dateInstance == null) {
406             dateInstance = new FullyQualifiedJavaType("java.util.Date"); //$NON-NLS-1$
407         }
408 
409         return dateInstance;
410     }
411 
412     /**
413      * Gets the criteria instance.
414      *
415      * @return the criteria instance
416      */
417     public static final FullyQualifiedJavaType getCriteriaInstance() {
418         if (criteriaInstance == null) {
419             criteriaInstance = new FullyQualifiedJavaType("Criteria"); //$NON-NLS-1$
420         }
421 
422         return criteriaInstance;
423     }
424 
425     /**
426      * Gets the generated criteria instance.
427      *
428      * @return the generated criteria instance
429      */
430     public static final FullyQualifiedJavaType getGeneratedCriteriaInstance() {
431         if (generatedCriteriaInstance == null) {
432             generatedCriteriaInstance = new FullyQualifiedJavaType(
433                     "GeneratedCriteria"); //$NON-NLS-1$
434         }
435 
436         return generatedCriteriaInstance;
437     }
438 
439     /*
440      * (non-Javadoc)
441      * 
442      * @see java.lang.Comparable#compareTo(java.lang.Object)
443      */
444     public int compareTo(FullyQualifiedJavaType other) {
445         return getFullyQualifiedName().compareTo(other.getFullyQualifiedName());
446     }
447 
448     /**
449      * Adds the type argument.
450      *
451      * @param type
452      *            the type
453      */
454     public void addTypeArgument(FullyQualifiedJavaType type) {
455         typeArguments.add(type);
456     }
457 
458     /**
459      * Parses the.
460      *
461      * @param fullTypeSpecification
462      *            the full type specification
463      */
464     private void parse(String fullTypeSpecification) {
465         String spec = fullTypeSpecification.trim();
466 
467         if (spec.startsWith("?")) { //$NON-NLS-1$
468             wildcardType = true;
469             spec = spec.substring(1).trim();
470             if (spec.startsWith("extends ")) { //$NON-NLS-1$
471                 boundedWildcard = true;
472                 extendsBoundedWildcard = true;
473                 spec = spec.substring(8);  // "extends ".length()
474             } else if (spec.startsWith("super ")) { //$NON-NLS-1$
475                 boundedWildcard = true;
476                 extendsBoundedWildcard = false;
477                 spec = spec.substring(6);  // "super ".length()
478             } else {
479                 boundedWildcard = false;
480             }
481             parse(spec);
482         } else {
483             int index = fullTypeSpecification.indexOf('<');
484             if (index == -1) {
485                 simpleParse(fullTypeSpecification);
486             } else {
487                 simpleParse(fullTypeSpecification.substring(0, index));
488                 int endIndex = fullTypeSpecification.lastIndexOf('>');
489                 if (endIndex == -1) {
490                     throw new RuntimeException(getString(
491                             "RuntimeError.22", fullTypeSpecification)); //$NON-NLS-1$
492                 }
493                 genericParse(fullTypeSpecification.substring(index, endIndex + 1));
494             }
495             
496             // this is far from a perfect test for detecting arrays, but is close
497             // enough for most cases.  It will not detect an improperly specified
498             // array type like byte], but it will detect byte[] and byte[   ]
499             // which are both valid
500             isArray = fullTypeSpecification.endsWith("]"); //$NON-NLS-1$
501         }
502     }
503 
504     /**
505      * Simple parse.
506      *
507      * @param typeSpecification
508      *            the type specification
509      */
510     private void simpleParse(String typeSpecification) {
511         baseQualifiedName = typeSpecification.trim();
512         if (baseQualifiedName.contains(".")) { //$NON-NLS-1$
513             packageName = getPackage(baseQualifiedName);
514             baseShortName = baseQualifiedName
515                     .substring(packageName.length() + 1);
516             int index = baseShortName.lastIndexOf('.');
517             if (index != -1) {
518                 baseShortName = baseShortName.substring(index + 1);
519             }
520             
521             if (JAVA_LANG.equals(packageName)) { //$NON-NLS-1$
522                 explicitlyImported = false;
523             } else {
524                 explicitlyImported = true;
525             }
526         } else {
527             baseShortName = baseQualifiedName;
528             explicitlyImported = false;
529             packageName = ""; //$NON-NLS-1$
530 
531             if ("byte".equals(baseQualifiedName)) { //$NON-NLS-1$
532                 primitive = true;
533                 primitiveTypeWrapper = PrimitiveTypeWrapper.getByteInstance();
534             } else if ("short".equals(baseQualifiedName)) { //$NON-NLS-1$
535                 primitive = true;
536                 primitiveTypeWrapper = PrimitiveTypeWrapper.getShortInstance();
537             } else if ("int".equals(baseQualifiedName)) { //$NON-NLS-1$
538                 primitive = true;
539                 primitiveTypeWrapper = PrimitiveTypeWrapper
540                         .getIntegerInstance();
541             } else if ("long".equals(baseQualifiedName)) { //$NON-NLS-1$
542                 primitive = true;
543                 primitiveTypeWrapper = PrimitiveTypeWrapper.getLongInstance();
544             } else if ("char".equals(baseQualifiedName)) { //$NON-NLS-1$
545                 primitive = true;
546                 primitiveTypeWrapper = PrimitiveTypeWrapper
547                         .getCharacterInstance();
548             } else if ("float".equals(baseQualifiedName)) { //$NON-NLS-1$
549                 primitive = true;
550                 primitiveTypeWrapper = PrimitiveTypeWrapper.getFloatInstance();
551             } else if ("double".equals(baseQualifiedName)) { //$NON-NLS-1$
552                 primitive = true;
553                 primitiveTypeWrapper = PrimitiveTypeWrapper.getDoubleInstance();
554             } else if ("boolean".equals(baseQualifiedName)) { //$NON-NLS-1$
555                 primitive = true;
556                 primitiveTypeWrapper = PrimitiveTypeWrapper
557                         .getBooleanInstance();
558             } else {
559                 primitive = false;
560                 primitiveTypeWrapper = null;
561             }
562         }
563     }
564 
565     /**
566      * Generic parse.
567      *
568      * @param genericSpecification
569      *            the generic specification
570      */
571     private void genericParse(String genericSpecification) {
572         int lastIndex = genericSpecification.lastIndexOf('>');
573         if (lastIndex == -1) {
574             // shouldn't happen - should be caught already, but just in case...
575             throw new RuntimeException(getString(
576                     "RuntimeError.22", genericSpecification)); //$NON-NLS-1$
577         }
578         String argumentString = genericSpecification.substring(1, lastIndex);
579         // need to find "," outside of a <> bounds
580         StringTokenizer st = new StringTokenizer(argumentString, ",<>", true); //$NON-NLS-1$
581         int openCount = 0;
582         StringBuilder sb = new StringBuilder();
583         while (st.hasMoreTokens()) {
584             String token = st.nextToken();
585             if ("<".equals(token)) { //$NON-NLS-1$
586                 sb.append(token);
587                 openCount++;
588             } else if (">".equals(token)) { //$NON-NLS-1$
589                 sb.append(token);
590                 openCount--;
591             } else if (",".equals(token)) { //$NON-NLS-1$
592                 if (openCount == 0) {
593                     typeArguments
594                             .add(new FullyQualifiedJavaType(sb.toString()));
595                     sb.setLength(0);
596                 } else {
597                     sb.append(token);
598                 }
599             } else {
600                 sb.append(token);
601             }
602         }
603 
604         if (openCount != 0) {
605             throw new RuntimeException(getString(
606                     "RuntimeError.22", genericSpecification)); //$NON-NLS-1$
607         }
608 
609         String finalType = sb.toString();
610         if (stringHasValue(finalType)) {
611             typeArguments.add(new FullyQualifiedJavaType(finalType));
612         }
613     }
614 
615     /**
616      * Returns the package name of a fully qualified type.
617      * 
618      * This method calculates the package as the part of the fully qualified name up to, but not including, the last
619      * element. Therefore, it does not support fully qualified inner classes. Not totally fool proof, but correct in
620      * most instances.
621      *
622      * @param baseQualifiedName
623      *            the base qualified name
624      * @return the package
625      */
626     private static String getPackage(String baseQualifiedName) {
627         int index = baseQualifiedName.lastIndexOf('.');
628         return baseQualifiedName.substring(0, index);
629     }
630 
631     /**
632      * Checks if is array.
633      *
634      * @return true, if is array
635      */
636     public boolean isArray() {
637         return isArray;
638     }
639 }