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