View Javadoc
1   /*
2    *    Copyright 2006-2023 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    *       https://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  public class FullyQualifiedJavaType implements Comparable<FullyQualifiedJavaType> {
26  
27      private static final String JAVA_LANG = "java.lang"; //$NON-NLS-1$
28  
29      private static FullyQualifiedJavaType intInstance = null;
30  
31      private static FullyQualifiedJavaType stringInstance = null;
32  
33      private static FullyQualifiedJavaType booleanPrimitiveInstance = null;
34  
35      private static FullyQualifiedJavaType objectInstance = null;
36  
37      private static FullyQualifiedJavaType dateInstance = null;
38  
39      private static FullyQualifiedJavaType criteriaInstance = null;
40  
41      private static FullyQualifiedJavaType generatedCriteriaInstance = null;
42  
43      /** The short name without any generic arguments. */
44      private String baseShortName;
45  
46      /** The fully qualified name without any generic arguments. */
47      private String baseQualifiedName;
48  
49      private boolean explicitlyImported;
50  
51      private String packageName;
52  
53      private boolean primitive;
54  
55      private boolean isArray;
56  
57      private PrimitiveTypeWrapper primitiveTypeWrapper;
58  
59      private final List<FullyQualifiedJavaType> typeArguments;
60  
61      // the following three values are used for dealing with wildcard types
62      private boolean wildcardType;
63  
64      private boolean boundedWildcard;
65  
66      private boolean extendsBoundedWildcard;
67  
68      /**
69       * Use this constructor to construct a generic type with the specified type parameters.
70       *
71       * @param fullTypeSpecification
72       *            the full type specification
73       */
74      public FullyQualifiedJavaType(String fullTypeSpecification) {
75          super();
76          typeArguments = new ArrayList<>();
77          parse(fullTypeSpecification);
78      }
79  
80      public boolean isExplicitlyImported() {
81          return explicitlyImported;
82      }
83  
84      /**
85       * Returns the fully qualified name - including any generic type parameters.
86       *
87       * @return Returns the fullyQualifiedName.
88       */
89      public String getFullyQualifiedName() {
90          StringBuilder sb = new StringBuilder();
91          if (wildcardType) {
92              sb.append('?');
93              if (boundedWildcard) {
94                  if (extendsBoundedWildcard) {
95                      sb.append(" extends "); //$NON-NLS-1$
96                  } else {
97                      sb.append(" super "); //$NON-NLS-1$
98                  }
99  
100                 sb.append(baseQualifiedName);
101             }
102         } else {
103             sb.append(baseQualifiedName);
104         }
105 
106         if (!typeArguments.isEmpty()) {
107             boolean first = true;
108             sb.append('<');
109             for (FullyQualifiedJavaType fqjt : typeArguments) {
110                 if (first) {
111                     first = false;
112                 } else {
113                     sb.append(", "); //$NON-NLS-1$
114                 }
115                 sb.append(fqjt.getFullyQualifiedName());
116 
117             }
118             sb.append('>');
119         }
120 
121         return sb.toString();
122     }
123 
124     public String getFullyQualifiedNameWithoutTypeParameters() {
125         return baseQualifiedName;
126     }
127 
128     /**
129      * Returns a list of Strings that are the fully qualified names of this type, and any generic type argument
130      * associated with this type.
131      *
132      * @return the import list
133      */
134     public List<String> getImportList() {
135         List<String> answer = new ArrayList<>();
136         if (isExplicitlyImported()) {
137             int index = baseShortName.indexOf('.');
138             if (index == -1) {
139                 answer.add(calculateActualImport(baseQualifiedName));
140             } else {
141                 // an inner class is specified, only import the top
142                 // level class
143                 String sb = packageName + '.' + calculateActualImport(baseShortName.substring(0, index));
144                 answer.add(sb);
145             }
146         }
147 
148         for (FullyQualifiedJavaType fqjt : typeArguments) {
149             answer.addAll(fqjt.getImportList());
150         }
151 
152         return answer;
153     }
154 
155     private String calculateActualImport(String name) {
156         String answer = name;
157         if (this.isArray()) {
158             int index = name.indexOf('[');
159             if (index != -1) {
160                 answer = name.substring(0, index);
161             }
162         }
163         return answer;
164     }
165 
166     public String getPackageName() {
167         return packageName;
168     }
169 
170     public String getShortName() {
171         StringBuilder sb = new StringBuilder();
172         if (wildcardType) {
173             sb.append('?');
174             if (boundedWildcard) {
175                 if (extendsBoundedWildcard) {
176                     sb.append(" extends "); //$NON-NLS-1$
177                 } else {
178                     sb.append(" super "); //$NON-NLS-1$
179                 }
180 
181                 sb.append(baseShortName);
182             }
183         } else {
184             sb.append(baseShortName);
185         }
186 
187         if (!typeArguments.isEmpty()) {
188             boolean first = true;
189             sb.append('<');
190             for (FullyQualifiedJavaType fqjt : typeArguments) {
191                 if (first) {
192                     first = false;
193                 } else {
194                     sb.append(", "); //$NON-NLS-1$
195                 }
196                 sb.append(fqjt.getShortName());
197 
198             }
199             sb.append('>');
200         }
201 
202         return sb.toString();
203     }
204 
205     public String getShortNameWithoutTypeArguments() {
206         return baseShortName;
207     }
208 
209     @Override
210     public boolean equals(Object obj) {
211         if (this == obj) {
212             return true;
213         }
214 
215         if (!(obj instanceof FullyQualifiedJavaType)) {
216             return false;
217         }
218 
219         FullyQualifiedJavaType other = (FullyQualifiedJavaType) obj;
220 
221         return getFullyQualifiedName().equals(other.getFullyQualifiedName());
222     }
223 
224     @Override
225     public int hashCode() {
226         return getFullyQualifiedName().hashCode();
227     }
228 
229     @Override
230     public String toString() {
231         return getFullyQualifiedName();
232     }
233 
234     public boolean isPrimitive() {
235         return primitive;
236     }
237 
238     public PrimitiveTypeWrapper getPrimitiveTypeWrapper() {
239         return primitiveTypeWrapper;
240     }
241 
242     public static FullyQualifiedJavaType getIntInstance() {
243         if (intInstance == null) {
244             intInstance = new FullyQualifiedJavaType("int"); //$NON-NLS-1$
245         }
246 
247         return intInstance;
248     }
249 
250     public static FullyQualifiedJavaType getNewListInstance() {
251         // always return a new instance because the type may be parameterized
252         return new FullyQualifiedJavaType("java.util.List"); //$NON-NLS-1$
253     }
254 
255     public static FullyQualifiedJavaType getNewHashMapInstance() {
256         // always return a new instance because the type may be parameterized
257         return new FullyQualifiedJavaType("java.util.HashMap"); //$NON-NLS-1$
258     }
259 
260     public static FullyQualifiedJavaType getNewArrayListInstance() {
261         // always return a new instance because the type may be parameterized
262         return new FullyQualifiedJavaType("java.util.ArrayList"); //$NON-NLS-1$
263     }
264 
265     public static FullyQualifiedJavaType getNewIteratorInstance() {
266         // always return a new instance because the type may be parameterized
267         return new FullyQualifiedJavaType("java.util.Iterator"); //$NON-NLS-1$
268     }
269 
270     public static FullyQualifiedJavaType getStringInstance() {
271         if (stringInstance == null) {
272             stringInstance = new FullyQualifiedJavaType("java.lang.String"); //$NON-NLS-1$
273         }
274 
275         return stringInstance;
276     }
277 
278     public static FullyQualifiedJavaType getBooleanPrimitiveInstance() {
279         if (booleanPrimitiveInstance == null) {
280             booleanPrimitiveInstance = new FullyQualifiedJavaType("boolean"); //$NON-NLS-1$
281         }
282 
283         return booleanPrimitiveInstance;
284     }
285 
286     public static FullyQualifiedJavaType getObjectInstance() {
287         if (objectInstance == null) {
288             objectInstance = new FullyQualifiedJavaType("java.lang.Object"); //$NON-NLS-1$
289         }
290 
291         return objectInstance;
292     }
293 
294     public static FullyQualifiedJavaType getDateInstance() {
295         if (dateInstance == null) {
296             dateInstance = new FullyQualifiedJavaType("java.util.Date"); //$NON-NLS-1$
297         }
298 
299         return dateInstance;
300     }
301 
302     public static FullyQualifiedJavaType getCriteriaInstance() {
303         if (criteriaInstance == null) {
304             criteriaInstance = new FullyQualifiedJavaType("Criteria"); //$NON-NLS-1$
305         }
306 
307         return criteriaInstance;
308     }
309 
310     public static FullyQualifiedJavaType getGeneratedCriteriaInstance() {
311         if (generatedCriteriaInstance == null) {
312             generatedCriteriaInstance = new FullyQualifiedJavaType(
313                     "GeneratedCriteria"); //$NON-NLS-1$
314         }
315 
316         return generatedCriteriaInstance;
317     }
318 
319     @Override
320     public int compareTo(FullyQualifiedJavaType other) {
321         return getFullyQualifiedName().compareTo(other.getFullyQualifiedName());
322     }
323 
324     public void addTypeArgument(FullyQualifiedJavaType type) {
325         typeArguments.add(type);
326     }
327 
328     private void parse(String fullTypeSpecification) {
329         String spec = fullTypeSpecification.trim();
330 
331         if (spec.startsWith("?")) { //$NON-NLS-1$
332             wildcardType = true;
333             spec = spec.substring(1).trim();
334             if (spec.startsWith("extends ")) { //$NON-NLS-1$
335                 boundedWildcard = true;
336                 extendsBoundedWildcard = true;
337                 spec = spec.substring(8); // "extends ".length()
338             } else if (spec.startsWith("super ")) { //$NON-NLS-1$
339                 boundedWildcard = true;
340                 extendsBoundedWildcard = false;
341                 spec = spec.substring(6); // "super ".length()
342             } else {
343                 boundedWildcard = false;
344             }
345             parse(spec);
346         } else {
347             int index = fullTypeSpecification.indexOf('<');
348             if (index == -1) {
349                 simpleParse(fullTypeSpecification);
350             } else {
351                 simpleParse(fullTypeSpecification.substring(0, index));
352                 int endIndex = fullTypeSpecification.lastIndexOf('>');
353                 if (endIndex == -1) {
354                     throw new RuntimeException(getString(
355                             "RuntimeError.22", fullTypeSpecification)); //$NON-NLS-1$
356                 }
357                 genericParse(fullTypeSpecification.substring(index, endIndex + 1));
358             }
359 
360             // this is far from a perfect test for detecting arrays, but is close
361             // enough for most cases. It will not detect an improperly specified
362             // array type like byte], but it will detect byte[] and byte[ ]
363             // which are both valid
364             isArray = fullTypeSpecification.endsWith("]"); //$NON-NLS-1$
365         }
366     }
367 
368     private void simpleParse(String typeSpecification) {
369         baseQualifiedName = typeSpecification.trim();
370         if (baseQualifiedName.contains(".")) { //$NON-NLS-1$
371             packageName = getPackage(baseQualifiedName);
372             baseShortName = baseQualifiedName
373                     .substring(packageName.length() + 1);
374             int index = baseShortName.lastIndexOf('.');
375             if (index != -1) {
376                 baseShortName = baseShortName.substring(index + 1);
377             }
378 
379             //$NON-NLS-1$
380             explicitlyImported = !JAVA_LANG.equals(packageName);
381         } else {
382             baseShortName = baseQualifiedName;
383             explicitlyImported = false;
384             packageName = ""; //$NON-NLS-1$
385 
386             switch (baseQualifiedName) {
387             case "byte":  //$NON-NLS-1$
388                 primitive = true;
389                 primitiveTypeWrapper = PrimitiveTypeWrapper.getByteInstance();
390                 break;
391             case "short":  //$NON-NLS-1$
392                 primitive = true;
393                 primitiveTypeWrapper = PrimitiveTypeWrapper.getShortInstance();
394                 break;
395             case "int":  //$NON-NLS-1$
396                 primitive = true;
397                 primitiveTypeWrapper = PrimitiveTypeWrapper.getIntegerInstance();
398                 break;
399             case "long":  //$NON-NLS-1$
400                 primitive = true;
401                 primitiveTypeWrapper = PrimitiveTypeWrapper.getLongInstance();
402                 break;
403             case "char":  //$NON-NLS-1$
404                 primitive = true;
405                 primitiveTypeWrapper = PrimitiveTypeWrapper.getCharacterInstance();
406                 break;
407             case "float":  //$NON-NLS-1$
408                 primitive = true;
409                 primitiveTypeWrapper = PrimitiveTypeWrapper.getFloatInstance();
410                 break;
411             case "double":  //$NON-NLS-1$
412                 primitive = true;
413                 primitiveTypeWrapper = PrimitiveTypeWrapper.getDoubleInstance();
414                 break;
415             case "boolean":  //$NON-NLS-1$
416                 primitive = true;
417                 primitiveTypeWrapper = PrimitiveTypeWrapper.getBooleanInstance();
418                 break;
419             default:
420                 primitive = false;
421                 primitiveTypeWrapper = null;
422                 break;
423             }
424         }
425     }
426 
427     private void genericParse(String genericSpecification) {
428         int lastIndex = genericSpecification.lastIndexOf('>');
429         if (lastIndex == -1) {
430             // shouldn't happen - should be caught already, but just in case...
431             throw new RuntimeException(getString(
432                     "RuntimeError.22", genericSpecification)); //$NON-NLS-1$
433         }
434         String argumentString = genericSpecification.substring(1, lastIndex);
435         // need to find "," outside of a <> bounds
436         StringTokenizer st = new StringTokenizer(argumentString, ",<>", true); //$NON-NLS-1$
437         int openCount = 0;
438         StringBuilder sb = new StringBuilder();
439         while (st.hasMoreTokens()) {
440             String token = st.nextToken();
441             if ("<".equals(token)) { //$NON-NLS-1$
442                 sb.append(token);
443                 openCount++;
444             } else if (">".equals(token)) { //$NON-NLS-1$
445                 sb.append(token);
446                 openCount--;
447             } else if (",".equals(token)) { //$NON-NLS-1$
448                 if (openCount == 0) {
449                     typeArguments
450                             .add(new FullyQualifiedJavaType(sb.toString()));
451                     sb.setLength(0);
452                 } else {
453                     sb.append(token);
454                 }
455             } else {
456                 sb.append(token);
457             }
458         }
459 
460         if (openCount != 0) {
461             throw new RuntimeException(getString(
462                     "RuntimeError.22", genericSpecification)); //$NON-NLS-1$
463         }
464 
465         String finalType = sb.toString();
466         if (stringHasValue(finalType)) {
467             typeArguments.add(new FullyQualifiedJavaType(finalType));
468         }
469     }
470 
471     /**
472      * Returns the package name of a fully qualified type.
473      *
474      * <p>This method calculates the package as the part of the fully qualified name up to, but not including, the last
475      * element. Therefore, it does not support fully qualified inner classes. Not totally fool proof, but correct in
476      * most instances.
477      *
478      * @param baseQualifiedName
479      *            the base qualified name
480      *
481      * @return the package
482      */
483     private static String getPackage(String baseQualifiedName) {
484         int index = baseQualifiedName.lastIndexOf('.');
485         return baseQualifiedName.substring(0, index);
486     }
487 
488     public boolean isArray() {
489         return isArray;
490     }
491 
492     public List<FullyQualifiedJavaType> getTypeArguments() {
493         return typeArguments;
494     }
495 }