001 /*
002 * Copyright 2008-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package griffon.util;
017
018 import javax.annotation.Nonnull;
019 import javax.annotation.Nullable;
020 import java.util.ArrayList;
021 import java.util.Arrays;
022 import java.util.Iterator;
023 import java.util.List;
024 import java.util.Locale;
025
026 /**
027 * Contains utility methods for converting between different name types,
028 * for example from class names -> property names and vice-versa. The
029 * key aspect of this class is that it has no dependencies outside the
030 * JDK!
031 */
032 public class GriffonNameUtils {
033 private static final String PROPERTY_SET_PREFIX = "set";
034 private static final String PROPERTY_GET_PREFIX = "get";
035
036 private static final String[] KEYWORDS = new String[]{
037 "abstract",
038 "assert",
039 "as",
040 "break",
041 "case",
042 "catch",
043 "class",
044 "const",
045 "continue",
046 "default",
047 "do",
048 "else",
049 "enum",
050 "extends",
051 "final",
052 "finally",
053 "for",
054 "goto",
055 "if",
056 "implements",
057 "import",
058 "in",
059 "instanceof",
060 "interface",
061 "native",
062 "new",
063 "package",
064 "private",
065 "protected",
066 "public",
067 "return",
068 "static",
069 "strictfp",
070 "super",
071 "switch",
072 "synchronized",
073 "this",
074 "throw",
075 "throws",
076 "transient",
077 "try",
078 "void",
079 "volatile",
080 "while"
081 };
082
083 private GriffonNameUtils() {
084 // prevent instantiation
085 }
086
087 /**
088 * Finds out if the given String is a Java/Groovy keyword.
089 *
090 * @param str The String to test
091 *
092 * @return <tt>true</tt> if the given String is a keyword, false otherwise
093 */
094 public static boolean isKeyword(String str) {
095 return !isBlank(str) && Arrays.binarySearch(KEYWORDS, str.toLowerCase(Locale.ENGLISH)) > -1;
096 }
097
098 /**
099 * Capitalizes a String (makes the first char uppercase) taking care
100 * of blank strings and single character strings.
101 *
102 * @param str The String to be capitalized
103 *
104 * @return Capitalized version of the target string if it is not blank
105 */
106 public static String capitalize(String str) {
107 if (isBlank(str)) { return str; }
108 if (str.length() == 1) { return str.toUpperCase(); }
109 return str.substring(0, 1).toUpperCase(Locale.ENGLISH) + str.substring(1);
110 }
111
112 /**
113 * Uncapitalizes a String (makes the first char lowercase) taking care
114 * of blank strings and single character strings.
115 *
116 * @param str The String to be uncapitalized
117 *
118 * @return Uncapitalized version of the target string if it is not blank
119 */
120 public static String uncapitalize(String str) {
121 if (isBlank(str)) { return str; }
122 if (str.length() == 1) { return String.valueOf(Character.toLowerCase(str.charAt(0))); }
123 return Character.toLowerCase(str.charAt(0)) + str.substring(1);
124 }
125
126 /**
127 * Retrieves the name of a setter for the specified property name
128 *
129 * @param propertyName The property name
130 *
131 * @return The setter equivalent
132 */
133 public static String getSetterName(String propertyName) {
134 return PROPERTY_SET_PREFIX + capitalize(propertyName);
135 }
136
137 /**
138 * Calculate the name for a getter method to retrieve the specified property
139 *
140 * @param propertyName The property name
141 *
142 * @return The name for the getter method for this property, if it were to exist, i.e. getConstraints
143 */
144 public static String getGetterName(String propertyName) {
145 return PROPERTY_GET_PREFIX + capitalize(propertyName);
146 }
147
148 /**
149 * Returns the class name for the given logical name and trailing name. For example "person" and "Controller" would evaluate to "PersonController"
150 *
151 * @param logicalName The logical name
152 * @param trailingName The trailing name
153 *
154 * @return The class name
155 */
156 public static String getClassName(String logicalName, String trailingName) {
157 if (isBlank(logicalName)) {
158 throw new IllegalArgumentException("Argument [logicalName] must not be null or blank");
159 }
160
161 String className = capitalize(logicalName);
162 if (trailingName != null) {
163 className = className + trailingName;
164 }
165 return className;
166 }
167
168 /**
169 * Returns the class name representation of the given name
170 *
171 * @param name The name to convert
172 *
173 * @return The property name representation
174 */
175 public static String getClassNameRepresentation(String name) {
176 StringBuilder buf = new StringBuilder();
177 if (name != null && name.length() > 0) {
178 String[] tokens = name.split("[^\\w\\d]");
179 for (String token1 : tokens) {
180 String token = token1.trim();
181 buf.append(capitalize(token));
182 }
183 }
184
185 return buf.toString();
186 }
187
188 /**
189 * Converts foo-bar into FooBar. Empty and null strings are returned
190 * as-is.
191 *
192 * @param name The lower case hyphen separated name
193 *
194 * @return The class name equivalent.
195 */
196 public static String getClassNameForLowerCaseHyphenSeparatedName(String name) {
197 // Handle null and empty strings.
198 if (isBlank(name)) { return name; }
199
200 if (name.indexOf('-') > -1) {
201 StringBuilder buf = new StringBuilder();
202 String[] tokens = name.split("-");
203 for (String token : tokens) {
204 if (token == null || token.length() == 0) { continue; }
205 buf.append(capitalize(token));
206 }
207 return buf.toString();
208 }
209
210 return capitalize(name);
211 }
212
213 /**
214 * Retrieves the logical class name of a Griffon artifact given the Griffon class
215 * and a specified trailing name
216 *
217 * @param clazz The class
218 * @param trailingName The trailing name such as "Controller" or "TagLib"
219 *
220 * @return The logical class name
221 */
222 public static String getLogicalName(Class<?> clazz, String trailingName) {
223 return getLogicalName(clazz.getName(), trailingName);
224 }
225
226 /**
227 * Retrieves the logical name of the class without the trailing name
228 *
229 * @param name The name of the class
230 * @param trailingName The trailing name
231 *
232 * @return The logical name
233 */
234 public static String getLogicalName(String name, String trailingName) {
235 if (!isBlank(name) && !isBlank(trailingName)) {
236 String shortName = getShortName(name);
237 if (shortName.endsWith(trailingName)) {
238 return shortName.substring(0, shortName.length() - trailingName.length());
239 }
240 }
241 return name;
242 }
243
244 public static String getLogicalPropertyName(String className, String trailingName) {
245 if (!isBlank(className) && !isBlank(trailingName)) {
246 if (className.length() == trailingName.length() + 1 && className.endsWith(trailingName)) {
247 return className.substring(0, 1).toLowerCase();
248 }
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 * <p>We could use Commons Lang for this, but we don't want GriffonNameUtils
410 * to have a dependency on any external library to minimise the number of
411 * dependencies required to bootstrap Griffon.</p>
412 *
413 * @param str The string to test.
414 *
415 * @return <code>true</code> if the string is <code>null</code>, or
416 * blank.
417 */
418 public static boolean isBlank(String str) {
419 if (str == null || str.length() == 0) {
420 return true;
421 }
422 for (char c : str.toCharArray()) {
423 if (!Character.isWhitespace(c)) {
424 return false;
425 }
426 }
427
428 return true;
429 }
430
431 /**
432 * Checks that the specified String is not {@code blank}. This
433 * method is designed primarily for doing parameter validation in methods
434 * and constructors, as demonstrated below:
435 * <blockquote><pre>
436 * public Foo(String str) {
437 * this.str = GriffonNameUtils.requireNonBlank(str);
438 * }
439 * </pre></blockquote>
440 *
441 * @param str the String to check for blank
442 *
443 * @return {@code str} if not {@code blank}
444 *
445 * @throws IllegalArgumentException if {@code str} is {@code blank}
446 */
447 public static String requireNonBlank(String str) {
448 if (isBlank(str)) {
449 throw new IllegalArgumentException();
450 }
451 return str;
452 }
453
454 /**
455 * Checks that the specified String is not {@code blank} and
456 * throws a customized {@link IllegalArgumentException} if it is. This method
457 * is designed primarily for doing parameter validation in methods and
458 * constructors with multiple parameters, as demonstrated below:
459 * <blockquote><pre>
460 * public Foo(String str) {
461 * this.str = GriffonNameUtils.requireNonBlank(str, "str must not be null");
462 * }
463 * </pre></blockquote>
464 *
465 * @param str the String to check for blank
466 * @param message detail message to be used in the event that a {@code
467 * IllegalArgumentException} is thrown
468 *
469 * @return {@code str} if not {@code blank}
470 *
471 * @throws IllegalArgumentException if {@code str} is {@code blank}
472 */
473 public static String requireNonBlank(String str, String message) {
474 if (isBlank(str)) {
475 throw new IllegalArgumentException(message);
476 }
477 return str;
478 }
479
480 /**
481 * Retrieves the hyphenated name representation of the supplied class. For example
482 * MyFunkyGriffonThingy would be my-funky-griffon-thingy.
483 *
484 * @param clazz The class to convert
485 *
486 * @return The hyphenated name representation
487 */
488 public static String getHyphenatedName(Class<?> clazz) {
489 if (clazz == null) {
490 return null;
491 }
492 return getHyphenatedName(clazz.getName());
493 }
494
495 /**
496 * Retrieves the hyphenated name representation of the given class name.
497 * For example MyFunkyGriffonThingy would be my-funky-griffon-thingy.
498 *
499 * @param name The class name to convert.
500 *
501 * @return The hyphenated name representation.
502 */
503 public static String getHyphenatedName(String name) {
504 if (isBlank(name)) { return name; }
505 if (name.endsWith(".groovy")) {
506 name = name.substring(0, name.length() - 7);
507 }
508 String naturalName = getNaturalName(getShortName(name));
509 return naturalName.replaceAll("\\s", "-").toLowerCase();
510 }
511
512 /**
513 * Concatenates the <code>toString()</code> representation of each
514 * item in this Iterable, with the given String as a separator between each item.
515 *
516 * @param self an Iterable of objects
517 * @param separator a String separator
518 *
519 * @return the joined String
520 */
521 @Nonnull
522 public static String join(@Nonnull Iterable<?> self, @Nullable String separator) {
523 StringBuilder buffer = new StringBuilder();
524 boolean first = true;
525
526 if (separator == null) { separator = ""; }
527
528 for (Object value : self) {
529 if (first) {
530 first = false;
531 } else {
532 buffer.append(separator);
533 }
534 buffer.append(String.valueOf(value));
535 }
536 return buffer.toString();
537 }
538
539 /**
540 * Applies single or double quotes to a string if it contains whitespace characters
541 *
542 * @param str the String to be surrounded by quotes
543 *
544 * @return a copy of the original String, surrounded by quotes
545 */
546 public static String quote(String str) {
547 if (isBlank(str)) { return str; }
548 for (int i = 0; i < str.length(); i++) {
549 if (Character.isWhitespace(str.charAt(i))) {
550 str = applyQuotes(str);
551 break;
552 }
553 }
554 return str;
555 }
556
557 /**
558 * Removes single or double quotes from a String
559 *
560 * @param str the String from which quotes will be removed
561 *
562 * @return the unquoted String
563 */
564 public static String unquote(String str) {
565 if (isBlank(str)) { return str; }
566 if ((str.startsWith("'") && str.endsWith("'")) ||
567 (str.startsWith("\"") && str.endsWith("\""))) {
568 return str.substring(1, str.length() - 1);
569 }
570 return str;
571 }
572
573 private static String applyQuotes(String string) {
574 if (string == null || string.length() == 0) {
575 return "\"\"";
576 }
577
578 char b;
579 char c = 0;
580 int i;
581 int len = string.length();
582 StringBuilder sb = new StringBuilder(len * 2);
583 String t;
584 char[] chars = string.toCharArray();
585 char[] buffer = new char[1030];
586 int bufferIndex = 0;
587 sb.append('"');
588 for (i = 0; i < len; i += 1) {
589 if (bufferIndex > 1024) {
590 sb.append(buffer, 0, bufferIndex);
591 bufferIndex = 0;
592 }
593 b = c;
594 c = chars[i];
595 switch (c) {
596 case '\\':
597 case '"':
598 buffer[bufferIndex++] = '\\';
599 buffer[bufferIndex++] = c;
600 break;
601 case '/':
602 if (b == '<') {
603 buffer[bufferIndex++] = '\\';
604 }
605 buffer[bufferIndex++] = c;
606 break;
607 default:
608 if (c < ' ') {
609 switch (c) {
610 case '\b':
611 buffer[bufferIndex++] = '\\';
612 buffer[bufferIndex++] = 'b';
613 break;
614 case '\t':
615 buffer[bufferIndex++] = '\\';
616 buffer[bufferIndex++] = 't';
617 break;
618 case '\n':
619 buffer[bufferIndex++] = '\\';
620 buffer[bufferIndex++] = 'n';
621 break;
622 case '\f':
623 buffer[bufferIndex++] = '\\';
624 buffer[bufferIndex++] = 'f';
625 break;
626 case '\r':
627 buffer[bufferIndex++] = '\\';
628 buffer[bufferIndex++] = 'r';
629 break;
630 default:
631 t = "000" + Integer.toHexString(c);
632 int tLength = t.length();
633 buffer[bufferIndex++] = '\\';
634 buffer[bufferIndex++] = 'u';
635 buffer[bufferIndex++] = t.charAt(tLength - 4);
636 buffer[bufferIndex++] = t.charAt(tLength - 3);
637 buffer[bufferIndex++] = t.charAt(tLength - 2);
638 buffer[bufferIndex++] = t.charAt(tLength - 1);
639 }
640 } else {
641 buffer[bufferIndex++] = c;
642 }
643 }
644 }
645 sb.append(buffer, 0, bufferIndex);
646 sb.append('"');
647 return sb.toString();
648 }
649 }
|