1
2
3
4
5
6 package dev.metaschema.databind.codegen;
7
8 import java.net.URI;
9 import java.net.URISyntaxException;
10 import java.util.ArrayList;
11 import java.util.List;
12 import java.util.Locale;
13 import java.util.Map;
14 import java.util.Set;
15
16 import dev.metaschema.core.util.ObjectUtils;
17 import edu.umd.cs.findbugs.annotations.NonNull;
18
19
20
21
22 public final class ClassUtils {
23 private static final Map<String, String> JAVA_NAME_MAPPER = Map.ofEntries(
24 Map.entry("Class", "Clazz"));
25
26 private ClassUtils() {
27
28 }
29
30
31
32
33
34
35
36
37
38 @SuppressWarnings("null")
39 @NonNull
40 public static String toPropertyName(@NonNull String name) {
41 String property = upperCamelCase(name);
42 return JAVA_NAME_MAPPER.getOrDefault(property, property);
43 }
44
45
46
47
48
49
50
51
52
53 @NonNull
54 public static String toVariableName(@NonNull String name) {
55 return lowerCamelCase(name);
56 }
57
58
59
60
61
62
63
64
65
66 @NonNull
67 public static String toClassName(@NonNull String name) {
68 return upperCamelCase(name);
69 }
70
71
72
73
74
75
76
77
78
79 @NonNull
80 public static String toPackageName(@NonNull String namespace) {
81 return getPackageFromNamespace(namespace);
82 }
83
84
85
86
87
88
89
90
91
92
93
94 @NonNull
95 private static String upperCamelCase(@NonNull String name) {
96 List<String> words = splitWords(name);
97 StringBuilder sb = new StringBuilder();
98 for (String word : words) {
99 if (!word.isEmpty()) {
100 sb.append(Character.toUpperCase(word.charAt(0)));
101 if (word.length() > 1) {
102 sb.append(word.substring(1).toLowerCase(Locale.ROOT));
103 }
104 }
105 }
106 return sb.length() > 0 ? ObjectUtils.notNull(sb.toString()) : name;
107 }
108
109
110
111
112
113
114
115
116
117
118
119 @NonNull
120 private static String lowerCamelCase(@NonNull String name) {
121 List<String> words = splitWords(name);
122 StringBuilder sb = new StringBuilder();
123 boolean first = true;
124 for (String word : words) {
125 if (!word.isEmpty()) {
126 if (first) {
127 sb.append(word.toLowerCase(Locale.ROOT));
128 first = false;
129 } else {
130 sb.append(Character.toUpperCase(word.charAt(0)));
131 if (word.length() > 1) {
132 sb.append(word.substring(1).toLowerCase(Locale.ROOT));
133 }
134 }
135 }
136 }
137 return sb.length() > 0 ? ObjectUtils.notNull(sb.toString()) : name;
138 }
139
140
141
142
143
144
145
146
147
148
149
150 @NonNull
151 private static List<String> splitWords(@NonNull String name) {
152 List<String> words = new ArrayList<>();
153 StringBuilder currentWord = new StringBuilder();
154
155 for (int i = 0; i < name.length(); i++) {
156 char ch = name.charAt(i);
157 if (ch == '-' || ch == '_' || ch == '.' || Character.isWhitespace(ch)) {
158
159 if (currentWord.length() > 0) {
160 words.add(currentWord.toString());
161 currentWord = new StringBuilder();
162 }
163 } else if (Character.isUpperCase(ch) && currentWord.length() > 0
164 && Character.isLowerCase(currentWord.charAt(currentWord.length() - 1))) {
165
166 words.add(currentWord.toString());
167 currentWord = new StringBuilder();
168 currentWord.append(ch);
169 } else {
170 currentWord.append(ch);
171 }
172 }
173 if (currentWord.length() > 0) {
174 words.add(currentWord.toString());
175 }
176 return words;
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 @NonNull
195 private static String getPackageFromNamespace(@NonNull String namespace) {
196 try {
197 URI uri = new URI(namespace);
198 StringBuilder sb = new StringBuilder();
199
200
201 String host = uri.getHost();
202 if (host != null && !host.isEmpty()) {
203 String[] hostParts = host.split("\\.");
204 for (int i = hostParts.length - 1; i >= 0; i--) {
205 if (sb.length() > 0) {
206 sb.append('.');
207 }
208 sb.append(normalizePackagePart(ObjectUtils.notNull(hostParts[i])));
209 }
210 }
211
212
213 String path = uri.getPath();
214 if (path != null && !path.isEmpty()) {
215 String[] pathParts = path.split("/");
216 for (String part : pathParts) {
217 if (!part.isEmpty()) {
218 if (sb.length() > 0) {
219 sb.append('.');
220 }
221 sb.append(normalizePackagePart(part));
222 }
223 }
224 }
225
226 return sb.length() > 0 ? ObjectUtils.notNull(sb.toString()) : "generated";
227 } catch (@SuppressWarnings("unused") URISyntaxException ex) {
228
229 return normalizePackagePart(ObjectUtils.notNull(namespace.replaceAll("[^a-zA-Z0-9]", "_")));
230 }
231 }
232
233
234
235
236
237
238
239
240
241
242
243 @NonNull
244 private static String normalizePackagePart(@NonNull String part) {
245 if (part.isEmpty()) {
246 return "_";
247 }
248
249 StringBuilder sb = new StringBuilder();
250 for (int i = 0; i < part.length(); i++) {
251 char ch = part.charAt(i);
252 if (i == 0) {
253 if (Character.isJavaIdentifierStart(ch)) {
254 sb.append(Character.toLowerCase(ch));
255 } else if (Character.isDigit(ch)) {
256 sb.append('_');
257 sb.append(ch);
258 } else {
259 sb.append('_');
260 }
261 } else {
262 if (Character.isJavaIdentifierPart(ch)) {
263 sb.append(Character.toLowerCase(ch));
264 } else if (ch == '-' || ch == '.') {
265
266 sb.append('_');
267 }
268
269 }
270 }
271
272 String result = ObjectUtils.notNull(sb.toString());
273
274 if (isJavaReservedWord(result)) {
275 return "_" + result;
276 }
277 return result.isEmpty() ? "_" : result;
278 }
279
280
281
282
283
284
285
286
287
288 private static final Set<String> JAVA_RESERVED_WORDS = Set.of(
289 "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
290 "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float",
291 "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native",
292 "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp",
293 "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void",
294 "volatile", "while", "true", "false", "null",
295
296 "module", "var", "yield", "record", "sealed", "permits");
297
298
299
300
301
302
303
304
305 private static boolean isJavaReservedWord(@NonNull String word) {
306 return JAVA_RESERVED_WORDS.contains(word);
307 }
308 }