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() == 1) return str.toUpperCase();
099 return str.substring(0, 1).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() == 0) continue;
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() + 1 && className.endsWith(trailingName)) {
230 return className.substring(0, 1).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() > 1 && Character.isUpperCase(name.charAt(0)) && Character.isUpperCase(name.charAt(1))) {
283 return name;
284 }
285
286 String propertyName = name.substring(0, 1).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() > 1 && 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 == 0 && 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 == null) separator = "";
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 }
|