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.kotlin;
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.HashSet;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.Set;
26  import java.util.StringTokenizer;
27  import java.util.function.Function;
28  import java.util.stream.Collectors;
29  import java.util.stream.Stream;
30  
31  public class FullyQualifiedKotlinType {
32  
33      private static final Set<String> AUTOMATIC_KOTLIN_PACKAGES = new HashSet<>();
34  
35      static {
36          AUTOMATIC_KOTLIN_PACKAGES.add("kotlin"); //$NON-NLS-1$
37          AUTOMATIC_KOTLIN_PACKAGES.add("kotlin.collections"); //$NON-NLS-1$
38      }
39  
40      private String packageName;
41      private final List<FullyQualifiedKotlinType> typeArguments = new ArrayList<>();
42      private String shortNameWithoutTypeArguments;
43      private boolean isExplicitlyImported;
44  
45      public FullyQualifiedKotlinType(String fullTypeSpecification) {
46          parse(Objects.requireNonNull(fullTypeSpecification).trim());
47      }
48  
49      public String getPackageName() {
50          return packageName;
51      }
52  
53      public String getShortNameWithoutTypeArguments() {
54          return shortNameWithoutTypeArguments;
55      }
56  
57      public String getShortNameWithTypeArguments() {
58          if (typeArguments.isEmpty()) {
59              return shortNameWithoutTypeArguments;
60          }
61  
62          return typeArguments.stream().map(FullyQualifiedKotlinType::getShortNameWithTypeArguments)
63                  .collect(Collectors.joining(", ", shortNameWithoutTypeArguments //$NON-NLS-1$
64                          + "<", ">")); //$NON-NLS-1$ //$NON-NLS-2$
65      }
66  
67      public List<FullyQualifiedKotlinType> getTypeArguments() {
68          return typeArguments;
69      }
70  
71      public void addTypeArgument(FullyQualifiedKotlinType typeArgument) {
72          typeArguments.add(typeArgument);
73      }
74  
75      /**
76       * Returns a list of Strings that are the fully qualified names of this type,
77       * and any generic type argument associated with this type.
78       *
79       * @return the import list
80       */
81      public Set<String> getImportList() {
82          Stream<String> thisImport;
83          if (isExplicitlyImported) {
84              thisImport = Stream.of(packageName + "." + shortNameWithoutTypeArguments); //$NON-NLS-1$
85          } else {
86              thisImport = Stream.empty();
87          }
88  
89          Stream<String> ss = typeArguments.stream()
90                  .map(FullyQualifiedKotlinType::getImportList)
91                  .flatMap(Set::stream);
92  
93          return Stream.of(thisImport, ss)
94                  .flatMap(Function.identity())
95                  .collect(Collectors.toSet());
96      }
97  
98      private void parse(String fullTypeSpecification) {
99          int index = fullTypeSpecification.indexOf('<');
100         if (index == -1) {
101             simpleParse(fullTypeSpecification);
102         } else {
103             simpleParse(fullTypeSpecification.substring(0, index));
104             int endIndex = fullTypeSpecification.lastIndexOf('>');
105             if (endIndex == -1) {
106                 throw new RuntimeException(getString("RuntimeError.22", fullTypeSpecification)); //$NON-NLS-1$
107             }
108             genericParse(fullTypeSpecification.substring(index, endIndex + 1));
109         }
110     }
111 
112     private void simpleParse(String typeSpecification) {
113         String baseQualifiedName = typeSpecification.trim();
114         if (baseQualifiedName.contains(".")) { //$NON-NLS-1$
115             packageName = getPackage(baseQualifiedName);
116             shortNameWithoutTypeArguments = baseQualifiedName.substring(packageName.length() + 1);
117             isExplicitlyImported = !AUTOMATIC_KOTLIN_PACKAGES.contains(packageName);
118         } else {
119             shortNameWithoutTypeArguments = baseQualifiedName;
120             isExplicitlyImported = false;
121             packageName = ""; //$NON-NLS-1$
122         }
123     }
124 
125     private void genericParse(String genericSpecification) {
126         int lastIndex = genericSpecification.lastIndexOf('>');
127         if (lastIndex == -1) {
128             // shouldn't happen - should be caught already, but just in case...
129             throw new RuntimeException(getString("RuntimeError.22", genericSpecification)); //$NON-NLS-1$
130         }
131         String argumentString = genericSpecification.substring(1, lastIndex);
132         // need to find "," outside of a <> bounds
133         StringTokenizer st = new StringTokenizer(argumentString, ",<>", true); //$NON-NLS-1$
134         int openCount = 0;
135         StringBuilder sb = new StringBuilder();
136         while (st.hasMoreTokens()) {
137             String token = st.nextToken();
138             if ("<".equals(token)) { //$NON-NLS-1$
139                 sb.append(token);
140                 openCount++;
141             } else if (">".equals(token)) { //$NON-NLS-1$
142                 sb.append(token);
143                 openCount--;
144             } else if (",".equals(token)) { //$NON-NLS-1$
145                 if (openCount == 0) {
146                     typeArguments.add(new FullyQualifiedKotlinType(sb.toString()));
147                     sb.setLength(0);
148                 } else {
149                     sb.append(token);
150                 }
151             } else {
152                 sb.append(token);
153             }
154         }
155 
156         if (openCount != 0) {
157             throw new RuntimeException(getString("RuntimeError.22", genericSpecification)); //$NON-NLS-1$
158         }
159 
160         String finalType = sb.toString();
161         if (stringHasValue(finalType)) {
162             typeArguments.add(new FullyQualifiedKotlinType(finalType));
163         }
164     }
165 
166     /**
167      * Returns the package name of a fully qualified type.
168      *
169      * <p>This method calculates the package as the part of the fully qualified name up
170      * to, but not including, the last element. Therefore, it does not support fully
171      * qualified inner classes. Not totally fool proof, but correct in most
172      * instances.
173      *
174      * @param baseQualifiedName the base qualified name
175      * @return the package
176      */
177     private static String getPackage(String baseQualifiedName) {
178         int index = baseQualifiedName.lastIndexOf('.');
179         return baseQualifiedName.substring(0, index);
180     }
181 }