001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2017 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package griffon.util;
019
020 import javax.annotation.Nonnull;
021 import javax.annotation.Nullable;
022 import java.util.ArrayList;
023 import java.util.Arrays;
024 import java.util.Iterator;
025 import java.util.List;
026 import java.util.Locale;
027
028 /**
029 * Contains utility methods for converting between different name types,
030 * for example from class names -> property names and vice-versa. The
031 * key aspect of this class is that it has no dependencies outside the
032 * JDK!
033 */
034 public class GriffonNameUtils {
035 private static final String PROPERTY_SET_PREFIX = "set";
036 private static final String PROPERTY_GET_PREFIX = "get";
037
038 private static final String[] KEYWORDS = new String[]{
039 "abstract",
040 "assert",
041 "as",
042 "break",
043 "case",
044 "catch",
045 "class",
046 "const",
047 "continue",
048 "default",
049 "do",
050 "else",
051 "enum",
052 "extends",
053 "final",
054 "finally",
055 "for",
056 "goto",
057 "if",
058 "implements",
059 "import",
060 "in",
061 "instanceof",
062 "interface",
063 "native",
064 "new",
065 "package",
066 "private",
067 "protected",
068 "public",
069 "return",
070 "static",
071 "strictfp",
072 "super",
073 "switch",
074 "synchronized",
075 "this",
076 "throw",
077 "throws",
078 "transient",
079 "try",
080 "void",
081 "volatile",
082 "while"
083 };
084
085 private GriffonNameUtils() {
086 // prevent instantiation
087 }
088
089 /**
090 * Finds out if the given String is a Java/Groovy keyword.
091 *
092 * @param str The String to test
093 *
094 * @return <tt>true</tt> if the given String is a keyword, false otherwise
095 */
096 public static boolean isKeyword(String str) {
097 return isNotBlank(str) && Arrays.binarySearch(KEYWORDS, str.toLowerCase(Locale.ENGLISH)) > -1;
098 }
099
100 /**
101 * Capitalizes a String (makes the first char uppercase) taking care
102 * of blank strings and single character strings.
103 *
104 * @param str The String to be capitalized
105 *
106 * @return Capitalized version of the target string if it is not blank
107 */
108 public static String capitalize(String str) {
109 if (isBlank(str)) { return str; }
110 if (str.length() == 1) { return str.toUpperCase(); }
111 return str.substring(0, 1).toUpperCase(Locale.ENGLISH) + str.substring(1);
112 }
113
114 /**
115 * Uncapitalizes a String (makes the first char lowercase) taking care
116 * of blank strings and single character strings.
117 *
118 * @param str The String to be uncapitalized
119 *
120 * @return Uncapitalized version of the target string if it is not blank
121 */
122 public static String uncapitalize(String str) {
123 if (isBlank(str)) { return str; }
124 if (str.length() == 1) { return String.valueOf(Character.toLowerCase(str.charAt(0))); }
125 return Character.toLowerCase(str.charAt(0)) + str.substring(1);
126 }
127
128 /**
129 * Retrieves the name of a setter for the specified property name
130 *
131 * @param propertyName The property name
132 *
133 * @return The setter equivalent
134 */
135 public static String getSetterName(String propertyName) {
136 return PROPERTY_SET_PREFIX + capitalize(propertyName);
137 }
138
139 /**
140 * Calculate the name for a getter method to retrieve the specified property
141 *
142 * @param propertyName The property name
143 *
144 * @return The name for the getter method for this property, if it were to exist, i.e. getConstraints
145 */
146 public static String getGetterName(String propertyName) {
147 return PROPERTY_GET_PREFIX + capitalize(propertyName);
148 }
149
150 /**
151 * Returns the class name for the given logical name and trailing name. For example "person" and "Controller" would evaluate to "PersonController"
152 *
153 * @param logicalName The logical name
154 * @param trailingName The trailing name
155 *
156 * @return The class name
157 */
158 public static String getClassName(String logicalName, String trailingName) {
159 if (isBlank(logicalName)) {
160 throw new IllegalArgumentException("Argument [logicalName] must not be null or blank");
161 }
162
163 String className = capitalize(logicalName);
164 if (trailingName != null) {
165 className = className + trailingName;
166 }
167 return className;
168 }
169
170 /**
171 * Returns the class name representation of the given name
172 *
173 * @param name The name to convert
174 *
175 * @return The property name representation
176 */
177 public static String getClassNameRepresentation(String name) {
178 StringBuilder buf = new StringBuilder();
179 if (name != null && name.length() > 0) {
180 String[] tokens = name.split("[^\\w\\d]");
181 for (String token1 : tokens) {
182 String token = token1.trim();
183 buf.append(capitalize(token));
184 }
185 }
186
187 return buf.toString();
188 }
189
190 /**
191 * Converts foo-bar into FooBar. Empty and null strings are returned
192 * as-is.
193 *
194 * @param name The lower case hyphen separated name
195 *
196 * @return The class name equivalent.
197 */
198 public static String getClassNameForLowerCaseHyphenSeparatedName(String name) {
199 // Handle null and empty strings.
200 if (isBlank(name)) { return name; }
201
202 if (name.indexOf('-') > -1) {
203 StringBuilder buf = new StringBuilder();
204 String[] tokens = name.split("-");
205 for (String token : tokens) {
206 if (token == null || token.length() == 0) { continue; }
207 buf.append(capitalize(token));
208 }
209 return buf.toString();
210 }
211
212 return capitalize(name);
213 }
214
215 /**
216 * Retrieves the logical class name of a Griffon artifact given the Griffon class
217 * and a specified trailing name
218 *
219 * @param clazz The class
220 * @param trailingName The trailing name such as "Controller" or "TagLib"
221 *
222 * @return The logical class name
223 */
224 public static String getLogicalName(Class<?> clazz, String trailingName) {
225 return getLogicalName(clazz.getName(), trailingName);
226 }
227
228 /**
229 * Retrieves the logical name of the class without the trailing name
230 *
231 * @param name The name of the class
232 * @param trailingName The trailing name
233 *
234 * @return The logical name
235 */
236 public static String getLogicalName(String name, String trailingName) {
237 if (isNotBlank(name) && isNotBlank(trailingName)) {
238 String shortName = getShortName(name);
239 if (shortName.endsWith(trailingName)) {
240 return shortName.substring(0, shortName.length() - trailingName.length());
241 }
242 }
243 return name;
244 }
245
246 public static String getLogicalPropertyName(String className, String trailingName) {
247 if (isNotBlank(className) && isNotBlank(trailingName) && className.length() == trailingName.length() + 1 && className.endsWith(trailingName)) {
248 return className.substring(0, 1).toLowerCase();
249 }
250 return getLogicalName(getPropertyName(className), trailingName);
251 }
252
253 /**
254 * Shorter version of getPropertyNameRepresentation
255 *
256 * @param name The name to convert
257 *
258 * @return The property name version
259 */
260 public static String getPropertyName(String name) {
261 return getPropertyNameRepresentation(name);
262 }
263
264 /**
265 * Shorter version of getPropertyNameRepresentation
266 *
267 * @param clazz The clazz to convert
268 *
269 * @return The property name version
270 */
271 public static String getPropertyName(Class<?> clazz) {
272 return getPropertyNameRepresentation(clazz);
273 }
274
275 /**
276 * Returns the property name equivalent for the specified class
277 *
278 * @param targetClass The class to get the property name for
279 *
280 * @return A property name representation of the class name (eg. MyClass becomes myClass)
281 */
282 public static String getPropertyNameRepresentation(Class<?> targetClass) {
283 String shortName = getShortName(targetClass);
284 return getPropertyNameRepresentation(shortName);
285 }
286
287 /**
288 * Returns the property name representation of the given name
289 *
290 * @param name The name to convert
291 *
292 * @return The property name representation
293 */
294 public static String getPropertyNameRepresentation(String name) {
295 if (isBlank(name)) { return name; }
296 // Strip any package from the name.
297 int pos = name.lastIndexOf('.');
298 if (pos != -1) {
299 name = name.substring(pos + 1);
300 }
301
302 // Check whether the name begins with two upper case letters.
303 if (name.length() > 1 && Character.isUpperCase(name.charAt(0)) && Character.isUpperCase(name.charAt(1))) {
304 return name;
305 }
306
307 String propertyName = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
308 if (propertyName.indexOf(' ') > -1) {
309 propertyName = propertyName.replaceAll("\\s", "");
310 }
311 return propertyName;
312 }
313
314 /**
315 * Converts foo-bar into fooBar
316 *
317 * @param name The lower case hyphen separated name
318 *
319 * @return The property name equivalent
320 */
321 public static String getPropertyNameForLowerCaseHyphenSeparatedName(String name) {
322 return getPropertyName(getClassNameForLowerCaseHyphenSeparatedName(name));
323 }
324
325 /**
326 * Returns the class name without the package prefix
327 *
328 * @param targetClass The class to get a short name for
329 *
330 * @return The short name of the class
331 */
332 public static String getShortName(Class<?> targetClass) {
333 String className = targetClass.getName();
334 return getShortName(className);
335 }
336
337 /**
338 * Returns the class name without the package prefix
339 *
340 * @param className The class name to get a short name for
341 *
342 * @return The short name of the class
343 */
344 public static String getShortName(String className) {
345 if (isBlank(className)) { return className; }
346 int i = className.lastIndexOf('.');
347 if (i > -1) {
348 className = className.substring(i + 1, className.length());
349 }
350 return className;
351 }
352
353 /**
354 * Converts a property name into its natural language equivalent eg ('firstName' becomes 'First Name')
355 *
356 * @param name The property name to convert
357 *
358 * @return The converted property name
359 */
360 public static String getNaturalName(String name) {
361 name = getShortName(name);
362 if (isBlank(name)) { return name; }
363 List<String> words = new ArrayList<>();
364 int i = 0;
365 char[] chars = name.toCharArray();
366 for (char c : chars) {
367 String w;
368 if (i >= words.size()) {
369 w = "";
370 words.add(i, w);
371 } else {
372 w = words.get(i);
373 }
374
375 if (Character.isLowerCase(c) || Character.isDigit(c)) {
376 if (Character.isLowerCase(c) && w.length() == 0) {
377 c = Character.toUpperCase(c);
378 } else if (w.length() > 1 && Character.isUpperCase(w.charAt(w.length() - 1))) {
379 w = "";
380 words.add(++i, w);
381 }
382
383 words.set(i, w + c);
384 } else if (Character.isUpperCase(c)) {
385 if ((i == 0 && w.length() == 0) || Character.isUpperCase(w.charAt(w.length() - 1))) {
386 words.set(i, w + c);
387 } else {
388 words.add(++i, String.valueOf(c));
389 }
390 }
391 }
392
393 StringBuilder buf = new StringBuilder();
394 for (Iterator<String> j = words.iterator(); j.hasNext(); ) {
395 String word = j.next();
396 buf.append(word);
397 if (j.hasNext()) {
398 buf.append(' ');
399 }
400 }
401 return buf.toString();
402 }
403
404 /**
405 * <p>Determines whether a given string is <code>null</code>, empty,
406 * or only contains whitespace. If it contains anything other than
407 * whitespace then the string is not considered to be blank and the
408 * method returns <code>false</code>.</p>
409 *
410 * @param str The string to test.
411 *
412 * @return <code>true</code> if the string is <code>null</code>, or
413 * blank.
414 */
415 public static boolean isBlank(String str) {
416 if (str == null || str.length() == 0) {
417 return true;
418 }
419 for (char c : str.toCharArray()) {
420 if (!Character.isWhitespace(c)) {
421 return false;
422 }
423 }
424
425 return true;
426 }
427
428 /**
429 * <p>Determines whether a given string is not <code>null</code>, empty,
430 * or only contains whitespace. If it contains anything other than
431 * whitespace then the string is not considered to be blank and the
432 * method returns <code>true</code>.</p>
433 *
434 * @param str The string to test.
435 *
436 * @return <code>true</code> if the string is not <code>null</code>, nor
437 * blank.
438 */
439 public static boolean isNotBlank(String str) {
440 return !isBlank(str);
441 }
442
443 /**
444 * Checks that the specified String is not {@code blank}. This
445 * method is designed primarily for doing parameter validation in methods
446 * and constructors, as demonstrated below:
447 * <blockquote><pre>
448 * public Foo(String str) {
449 * this.str = GriffonNameUtils.requireNonBlank(str);
450 * }
451 * </pre></blockquote>
452 *
453 * @param str the String to check for blank
454 *
455 * @return {@code str} if not {@code blank}
456 *
457 * @throws IllegalArgumentException if {@code str} is {@code blank}
458 */
459 public static String requireNonBlank(String str) {
460 if (isBlank(str)) {
461 throw new IllegalArgumentException();
462 }
463 return str;
464 }
465
466 /**
467 * Checks that the specified String is not {@code blank} and
468 * throws a customized {@link IllegalArgumentException} if it is. This method
469 * is designed primarily for doing parameter validation in methods and
470 * constructors with multiple parameters, as demonstrated below:
471 * <blockquote><pre>
472 * public Foo(String str) {
473 * this.str = GriffonNameUtils.requireNonBlank(str, "str must not be null");
474 * }
475 * </pre></blockquote>
476 *
477 * @param str the String to check for blank
478 * @param message detail message to be used in the event that a {@code
479 * IllegalArgumentException} is thrown
480 *
481 * @return {@code str} if not {@code blank}
482 *
483 * @throws IllegalArgumentException if {@code str} is {@code blank}
484 */
485 public static String requireNonBlank(String str, String message) {
486 if (isBlank(str)) {
487 throw new IllegalArgumentException(message);
488 }
489 return str;
490 }
491
492 /**
493 * Retrieves the hyphenated name representation of the supplied class. For example
494 * MyFunkyGriffonThingy would be my-funky-griffon-thingy.
495 *
496 * @param clazz The class to convert
497 *
498 * @return The hyphenated name representation
499 */
500 public static String getHyphenatedName(Class<?> clazz) {
501 if (clazz == null) {
502 return null;
503 }
504 return getHyphenatedName(clazz.getName());
505 }
506
507 /**
508 * Retrieves the hyphenated name representation of the given class name.
509 * For example MyFunkyGriffonThingy would be my-funky-griffon-thingy.
510 *
511 * @param name The class name to convert.
512 *
513 * @return The hyphenated name representation.
514 */
515 public static String getHyphenatedName(String name) {
516 if (isBlank(name)) { return name; }
517 if (name.endsWith(".groovy")) {
518 name = name.substring(0, name.length() - 7);
519 }
520 String naturalName = getNaturalName(getShortName(name));
521 return naturalName.replaceAll("\\s", "-").toLowerCase();
522 }
523
524 /**
525 * Concatenates the <code>toString()</code> representation of each
526 * item in this Iterable, with the given String as a separator between each item.
527 *
528 * @param self an Iterable of objects
529 * @param separator a String separator
530 *
531 * @return the joined String
532 */
533 @Nonnull
534 public static String join(@Nonnull Iterable<?> self, @Nullable String separator) {
535 StringBuilder buffer = new StringBuilder();
536 boolean first = true;
537
538 if (separator == null) { separator = ""; }
539
540 for (Object value : self) {
541 if (first) {
542 first = false;
543 } else {
544 buffer.append(separator);
545 }
546 buffer.append(String.valueOf(value));
547 }
548 return buffer.toString();
549 }
550
551 /**
552 * Applies single or double quotes to a string if it contains whitespace characters
553 *
554 * @param str the String to be surrounded by quotes
555 *
556 * @return a copy of the original String, surrounded by quotes
557 */
558 public static String quote(String str) {
559 if (isBlank(str)) { return str; }
560 for (int i = 0; i < str.length(); i++) {
561 if (Character.isWhitespace(str.charAt(i))) {
562 str = applyQuotes(str);
563 break;
564 }
565 }
566 return str;
567 }
568
569 /**
570 * Removes single or double quotes from a String
571 *
572 * @param str the String from which quotes will be removed
573 *
574 * @return the unquoted String
575 */
576 public static String unquote(String str) {
577 if (isBlank(str)) { return str; }
578 if ((str.startsWith("'") && str.endsWith("'")) ||
579 (str.startsWith("\"") && str.endsWith("\""))) {
580 return str.substring(1, str.length() - 1);
581 }
582 return str;
583 }
584
585 private static String applyQuotes(String string) {
586 if (string == null || string.length() == 0) {
587 return "\"\"";
588 }
589
590 char b;
591 char c = 0;
592 int i;
593 int len = string.length();
594 StringBuilder sb = new StringBuilder(len * 2);
595 String t;
596 char[] chars = string.toCharArray();
597 char[] buffer = new char[1030];
598 int bufferIndex = 0;
599 sb.append('"');
600 for (i = 0; i < len; i += 1) {
601 if (bufferIndex > 1024) {
602 sb.append(buffer, 0, bufferIndex);
603 bufferIndex = 0;
604 }
605 b = c;
606 c = chars[i];
607 switch (c) {
608 case '\\':
609 case '"':
610 buffer[bufferIndex++] = '\\';
611 buffer[bufferIndex++] = c;
612 break;
613 case '/':
614 if (b == '<') {
615 buffer[bufferIndex++] = '\\';
616 }
617 buffer[bufferIndex++] = c;
618 break;
619 default:
620 if (c < ' ') {
621 switch (c) {
622 case '\b':
623 buffer[bufferIndex++] = '\\';
624 buffer[bufferIndex++] = 'b';
625 break;
626 case '\t':
627 buffer[bufferIndex++] = '\\';
628 buffer[bufferIndex++] = 't';
629 break;
630 case '\n':
631 buffer[bufferIndex++] = '\\';
632 buffer[bufferIndex++] = 'n';
633 break;
634 case '\f':
635 buffer[bufferIndex++] = '\\';
636 buffer[bufferIndex++] = 'f';
637 break;
638 case '\r':
639 buffer[bufferIndex++] = '\\';
640 buffer[bufferIndex++] = 'r';
641 break;
642 default:
643 t = "000" + Integer.toHexString(c);
644 int tLength = t.length();
645 buffer[bufferIndex++] = '\\';
646 buffer[bufferIndex++] = 'u';
647 buffer[bufferIndex++] = t.charAt(tLength - 4);
648 buffer[bufferIndex++] = t.charAt(tLength - 3);
649 buffer[bufferIndex++] = t.charAt(tLength - 2);
650 buffer[bufferIndex++] = t.charAt(tLength - 1);
651 }
652 } else {
653 buffer[bufferIndex++] = c;
654 }
655 }
656 }
657 sb.append(buffer, 0, bufferIndex);
658 sb.append('"');
659 return sb.toString();
660 }
661 }
|