|
36 | 36 | import java.io.UnsupportedEncodingException; |
37 | 37 | import java.lang.reflect.Array; |
38 | 38 | import java.lang.reflect.Method; |
| 39 | +import static java.lang.Character.isLetter; |
| 40 | +import static java.lang.Character.isLowerCase; |
| 41 | +import static java.lang.Character.isUpperCase; |
| 42 | +import static java.lang.Character.isDigit; |
| 43 | +import static java.lang.Character.toLowerCase; |
39 | 44 | import java.math.BigDecimal; |
40 | 45 | import java.math.BigInteger; |
41 | 46 | import java.util.HashMap; |
@@ -289,41 +294,91 @@ public static Object unwrapJavaValue(Ruby runtime, IRubyObject obj, String error |
289 | 294 | } |
290 | 295 | } |
291 | 296 |
|
292 | | - private static final Pattern JAVA_PROPERTY_CHOPPER = Pattern.compile("(get|set|is)([A-Z0-9])(.*)"); |
| 297 | + /** |
| 298 | + * For methods that match /(get|set|is)([A-Z0-9])(.*)/, return the "name" |
| 299 | + * part of the property with leading lower-case. |
| 300 | + * |
| 301 | + * Does not use regex for performance reasons. |
| 302 | + * |
| 303 | + * @param beanMethodName the bean method from which to extract a name |
| 304 | + * @return the bean property name |
| 305 | + */ |
293 | 306 | public static String getJavaPropertyName(String beanMethodName) { |
294 | | - Matcher m = JAVA_PROPERTY_CHOPPER.matcher(beanMethodName); |
295 | | - |
296 | | - if (!m.find()) return null; |
297 | | - String javaPropertyName = m.group(2).toLowerCase() + m.group(3); |
298 | | - return javaPropertyName; |
| 307 | + int length = beanMethodName.length(); |
| 308 | + char ch; |
| 309 | + if ((beanMethodName.startsWith("get") || beanMethodName.startsWith("set")) && length > 3) { |
| 310 | + if (isUpperDigit(ch = beanMethodName.charAt(3))) { |
| 311 | + if (length == 4) { |
| 312 | + return Character.toString(toLowerCase(ch)); |
| 313 | + } else { |
| 314 | + return "" + toLowerCase(ch) + beanMethodName.substring(4); |
| 315 | + } |
| 316 | + } |
| 317 | + } else if (beanMethodName.startsWith("is") && length > 2) { |
| 318 | + if (isUpperDigit(ch = beanMethodName.charAt(2))) { |
| 319 | + if (length == 3) { |
| 320 | + return Character.toString(toLowerCase(ch)); |
| 321 | + } else { |
| 322 | + return "" + toLowerCase(ch) + beanMethodName.substring(3); |
| 323 | + } |
| 324 | + } |
| 325 | + } |
| 326 | + return null; |
299 | 327 | } |
300 | 328 |
|
301 | | - // This pattern says, "either match a lowercase letter or number |
302 | | - // followed by an uppercase letter, or match any letter or number |
303 | | - // followed by an uppercase letter and a lowercase letter." |
304 | | - private static final Pattern CAMEL_CASE_SPLITTER = Pattern.compile("(([a-z0-9])([A-Z])|([A-Za-z0-9])([A-Z][a-z]))"); |
| 329 | + /** |
| 330 | + * For methods that match /(([a-z0-9])([A-Z])|([A-Za-z0-9])([A-Z][a-z]))/, |
| 331 | + * return the snake-cased equivalent (inserting _ between groups 2 and 3 or |
| 332 | + * 4 and 5). |
| 333 | + * |
| 334 | + * Does not use regex for performance reasons. |
| 335 | + * |
| 336 | + * @param javaCasedName the camelCased name to convert |
| 337 | + * @return the snake_cased result |
| 338 | + */ |
305 | 339 | public static String getRubyCasedName(String javaCasedName) { |
306 | | - Matcher m = CAMEL_CASE_SPLITTER.matcher(javaCasedName); |
307 | | - // We do this replace loop manually because Android's Matcher produces null for unmatched $ groups. |
308 | | - // See JRUBY-5541 |
309 | | - if (m.find()) { |
310 | | - StringBuffer buffer = new StringBuffer(); |
311 | | - m.reset(); |
312 | | - while (m.find()) { |
313 | | - if (m.group(2) != null) { |
314 | | - // first part matched |
315 | | - m.appendReplacement(buffer, "$2_$3"); |
316 | | - } else { |
317 | | - // second part matched { |
318 | | - m.appendReplacement(buffer, "$4_$5"); |
319 | | - } |
| 340 | + StringBuilder b = new StringBuilder(); |
| 341 | + char[] chars = javaCasedName.toCharArray(); |
| 342 | + int behind = 0; |
| 343 | + for (int i = 0; i < chars.length; i++) { |
| 344 | + if (behind < 2) { |
| 345 | + behind++; |
| 346 | + } else { |
| 347 | + behind = consume(b, chars, i, behind); |
| 348 | + } |
| 349 | + } |
| 350 | + if (behind > 0) { |
| 351 | + if (behind > 1) { |
| 352 | + b.append(toLowerCase(chars[chars.length - 2])); |
320 | 353 | } |
321 | | - m.appendTail(buffer); |
322 | | - return buffer.toString().toLowerCase(); |
| 354 | + b.append(toLowerCase(chars[chars.length - 1])); |
| 355 | + } |
| 356 | + return b.toString(); |
| 357 | + } |
| 358 | + |
| 359 | + private static int consume(StringBuilder b, char[] chars, int i, int behind) { |
| 360 | + char cur, prev, prev2; |
| 361 | + if (isLowerDigit(prev2 = chars[i - 2]) && isUpperCase(prev = chars[i - 1])) { |
| 362 | + b.append(prev2).append('_').append(toLowerCase(prev)); |
| 363 | + return 1; |
| 364 | + } else if (isLetterDigit(prev2) && isUpperCase(prev = chars[i - 1]) && isLowerCase(cur = chars[i])) { |
| 365 | + b.append(toLowerCase(prev2)).append('_').append(toLowerCase(prev)).append(cur); |
| 366 | + return 0; |
323 | 367 | } else { |
324 | | - return javaCasedName; |
| 368 | + b.append(toLowerCase(prev2)); |
| 369 | + return 2; |
325 | 370 | } |
326 | 371 | } |
| 372 | + |
| 373 | + private static boolean isUpperDigit(char c) { |
| 374 | + return isUpperCase(c) || isDigit(c); |
| 375 | + } |
| 376 | + private static boolean isLowerDigit(char c) { |
| 377 | + return isLowerCase(c) || isDigit(c); |
| 378 | + } |
| 379 | + private static boolean isLetterDigit(char c) { |
| 380 | + return isLetter(c) || isDigit(c); |
| 381 | + } |
327 | 382 |
|
328 | 383 | private static final Pattern RUBY_CASE_SPLITTER = Pattern.compile("([a-z][0-9]*)_([a-z])"); |
329 | 384 | public static String getJavaCasedName(String javaCasedName) { |
|
0 commit comments