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