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() == 1) return str.toUpperCase();
103 return str.substring(0, 1).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() == 0) continue;
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() + 1 && className.endsWith(trailingName)) {
234 return className.substring(0, 1).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() > 1 && Character.isUpperCase(name.charAt(0)) && Character.isUpperCase(name.charAt(1))) {
287 return name;
288 }
289
290 String propertyName = name.substring(0, 1).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() > 1 && 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 == 0 && 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 == null) separator = "";
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 }
|