1 | /* |
2 | * Copyright (C) Christian Schulte, 2012-235 |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * |
9 | * o Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * |
12 | * o Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in |
14 | * the documentation and/or other materials provided with the |
15 | * distribution. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
19 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
20 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, |
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | * |
28 | * $JOMC: JavaTypeName.java 4804 2013-04-22 05:07:33Z schulte $ |
29 | * |
30 | */ |
31 | package org.jomc.model; |
32 | |
33 | import java.io.Serializable; |
34 | import java.lang.ref.Reference; |
35 | import java.lang.ref.SoftReference; |
36 | import java.text.MessageFormat; |
37 | import java.text.ParseException; |
38 | import java.text.ParsePosition; |
39 | import java.util.ArrayList; |
40 | import java.util.Collections; |
41 | import java.util.HashMap; |
42 | import java.util.LinkedList; |
43 | import java.util.List; |
44 | import java.util.Locale; |
45 | import java.util.Map; |
46 | import java.util.ResourceBundle; |
47 | |
48 | /** |
49 | * Data type of a Java type name. |
50 | * <p>This class supports parsing of Java type names as specified in the |
51 | * Java Language Specification - Java SE 7 Edition - Chapters 3.8ff, 6.5 and 18.</p> |
52 | * |
53 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
54 | * @version $JOMC: JavaTypeName.java 4804 2013-04-22 05:07:33Z schulte $ |
55 | * @see #parse(java.lang.String) |
56 | * @see #valueOf(java.lang.String) |
57 | * @since 1.4 |
58 | */ |
59 | public final class JavaTypeName implements Serializable |
60 | { |
61 | |
62 | /** |
63 | * Data type of an argument of a parameterized Java type name. |
64 | * |
65 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
66 | * @version $JOMC: JavaTypeName.java 4804 2013-04-22 05:07:33Z schulte $ |
67 | * @since 1.4 |
68 | */ |
69 | public static final class Argument implements Serializable |
70 | { |
71 | |
72 | /** |
73 | * Flag indicating the argument is a wildcard. |
74 | * @serial |
75 | */ |
76 | private boolean wildcard; |
77 | |
78 | /** |
79 | * The wildcard bounds of the argument. |
80 | * @serial |
81 | */ |
82 | private String wildcardBounds; |
83 | |
84 | /** |
85 | * The type name of the argument. |
86 | * @serial |
87 | */ |
88 | private JavaTypeName typeName; |
89 | |
90 | /** Cached string representation. */ |
91 | private transient String cachedString; |
92 | |
93 | /** Serial version UID for backwards compatibility with 1.4.x object streams. */ |
94 | private static final long serialVersionUID = -6515267147665760819L; |
95 | |
96 | /** Create a new {@code Argument} instance. */ |
97 | private Argument() |
98 | { |
99 | super(); |
100 | } |
101 | |
102 | /** |
103 | * Gets a flag indicating the argument is a wildcard argument. |
104 | * |
105 | * @return {@code true}, if the argument is a wildcard argument; {@code false}, else. |
106 | */ |
107 | public boolean isWildcard() |
108 | { |
109 | return this.wildcard; |
110 | } |
111 | |
112 | /** |
113 | * Gets the wildcard bounds of the argument. |
114 | * |
115 | * @return The wildcard bounds of the argument or {@code null}. |
116 | */ |
117 | public String getWildcardBounds() |
118 | { |
119 | return this.wildcardBounds; |
120 | } |
121 | |
122 | /** |
123 | * Gets the type name of the argument. |
124 | * |
125 | * @return The type name of the argument or {@code null}, if the argument is a wildcard argument. |
126 | */ |
127 | public JavaTypeName getTypeName() |
128 | { |
129 | return this.typeName; |
130 | } |
131 | |
132 | /** |
133 | * Creates a string representation of the instance. |
134 | * |
135 | * @return A string representation of the instance. |
136 | */ |
137 | @Override |
138 | public String toString() |
139 | { |
140 | if ( this.cachedString == null ) |
141 | { |
142 | final StringBuilder builder = new StringBuilder( 128 ); |
143 | |
144 | if ( this.isWildcard() ) |
145 | { |
146 | builder.append( "?" ); |
147 | |
148 | if ( this.getWildcardBounds() != null && this.getTypeName() != null ) |
149 | { |
150 | builder.append( " " ).append( this.getWildcardBounds() ).append( " " ). |
151 | append( this.getTypeName() ); |
152 | |
153 | } |
154 | } |
155 | else |
156 | { |
157 | builder.append( this.getTypeName() ); |
158 | } |
159 | |
160 | this.cachedString = builder.toString(); |
161 | } |
162 | |
163 | return this.cachedString; |
164 | } |
165 | |
166 | } |
167 | |
168 | /** |
169 | * Java type name of class {@code Boolean}. |
170 | * @see Boolean |
171 | */ |
172 | public static final JavaTypeName BOOLEAN; |
173 | |
174 | /** |
175 | * Java type name of basic type {@code boolean}. |
176 | * @see Boolean#TYPE |
177 | */ |
178 | public static final JavaTypeName BOOLEAN_TYPE; |
179 | |
180 | /** |
181 | * Java type name of class {@code Byte}. |
182 | * @see Byte |
183 | */ |
184 | public static final JavaTypeName BYTE; |
185 | |
186 | /** |
187 | * Java type name of basic type {@code byte}. |
188 | * @see Byte#TYPE |
189 | */ |
190 | public static final JavaTypeName BYTE_TYPE; |
191 | |
192 | /** |
193 | * Java type name of class {@code Character}. |
194 | * @see Character |
195 | */ |
196 | public static final JavaTypeName CHARACTER; |
197 | |
198 | /** |
199 | * Java type name of basic type {@code char}. |
200 | * @see Character#TYPE |
201 | */ |
202 | public static final JavaTypeName CHARACTER_TYPE; |
203 | |
204 | /** |
205 | * Java type name of class {@code Double}. |
206 | * @see Double |
207 | */ |
208 | public static final JavaTypeName DOUBLE; |
209 | |
210 | /** |
211 | * Java type name of basic type {@code double}. |
212 | * @see Double#TYPE |
213 | */ |
214 | public static final JavaTypeName DOUBLE_TYPE; |
215 | |
216 | /** |
217 | * Java type name of class {@code Float}. |
218 | * @see Float |
219 | */ |
220 | public static final JavaTypeName FLOAT; |
221 | |
222 | /** |
223 | * Java type name of basic type {@code float}. |
224 | * @see Float#TYPE |
225 | */ |
226 | public static final JavaTypeName FLOAT_TYPE; |
227 | |
228 | /** |
229 | * Java type name of class {@code Integer}. |
230 | * @see Integer |
231 | */ |
232 | public static final JavaTypeName INTEGER; |
233 | |
234 | /** |
235 | * Java type name of basic type {@code int}. |
236 | * @see Integer#TYPE |
237 | */ |
238 | public static final JavaTypeName INTEGER_TYPE; |
239 | |
240 | /** |
241 | * Java type name of class {@code Long}. |
242 | * @see Long |
243 | */ |
244 | public static final JavaTypeName LONG; |
245 | |
246 | /** |
247 | * Java type name of basic type {@code long}. |
248 | * @see Long#TYPE |
249 | */ |
250 | public static final JavaTypeName LONG_TYPE; |
251 | |
252 | /** |
253 | * Java type name of class {@code Short}. |
254 | * @see Short |
255 | */ |
256 | public static final JavaTypeName SHORT; |
257 | |
258 | /** |
259 | * Java type name of basic type {@code short}. |
260 | * @see Short#TYPE |
261 | */ |
262 | public static final JavaTypeName SHORT_TYPE; |
263 | |
264 | /** |
265 | * The array dimension of the type name. |
266 | * @serial |
267 | */ |
268 | private int dimension; |
269 | |
270 | /** |
271 | * The flag indicating the type name denotes a primitive type. |
272 | * @serial |
273 | */ |
274 | private boolean primitive; |
275 | |
276 | /** |
277 | * The class name of the type name. |
278 | * @serial |
279 | */ |
280 | private String className; |
281 | |
282 | /** |
283 | * The qualified package name of the type name. |
284 | * @serial |
285 | */ |
286 | private String packageName; |
287 | |
288 | /** |
289 | * The qualified name of the type name. |
290 | * @serial |
291 | */ |
292 | private String qualifiedName; |
293 | |
294 | /** |
295 | * The simple name of the type name. |
296 | * @serial |
297 | */ |
298 | private String simpleName; |
299 | |
300 | /** |
301 | * The arguments of the type name. |
302 | * @serial |
303 | */ |
304 | private volatile List<Argument> arguments; |
305 | |
306 | /** Cached string representation. */ |
307 | private transient String cachedString; |
308 | |
309 | /** Cached instances. */ |
310 | private static volatile Reference<Map<String, JavaTypeName>> cache; |
311 | |
312 | /** Mappings of basic type name to class name encoding. */ |
313 | private static final Map<String, String> CLASSNAME_ENCODINGS = new HashMap<String, String>( 8 ); |
314 | |
315 | /** Serial version UID for backwards compatibility with 1.4.x object streams. */ |
316 | private static final long serialVersionUID = -4258949347035910249L; |
317 | |
318 | static |
319 | { |
320 | CLASSNAME_ENCODINGS.put( "boolean", "Z" ); |
321 | CLASSNAME_ENCODINGS.put( "byte", "B" ); |
322 | CLASSNAME_ENCODINGS.put( "char", "C" ); |
323 | CLASSNAME_ENCODINGS.put( "double", "D" ); |
324 | CLASSNAME_ENCODINGS.put( "float", "F" ); |
325 | CLASSNAME_ENCODINGS.put( "int", "I" ); |
326 | CLASSNAME_ENCODINGS.put( "long", "J" ); |
327 | CLASSNAME_ENCODINGS.put( "short", "S" ); |
328 | |
329 | BOOLEAN = JavaTypeName.valueOf( Boolean.class.getName() ); |
330 | BOOLEAN_TYPE = JavaTypeName.valueOf( Boolean.TYPE.getName() ); |
331 | BYTE = JavaTypeName.valueOf( Byte.class.getName() ); |
332 | BYTE_TYPE = JavaTypeName.valueOf( Byte.TYPE.getName() ); |
333 | CHARACTER = JavaTypeName.valueOf( Character.class.getName() ); |
334 | CHARACTER_TYPE = JavaTypeName.valueOf( Character.TYPE.getName() ); |
335 | DOUBLE = JavaTypeName.valueOf( Double.class.getName() ); |
336 | DOUBLE_TYPE = JavaTypeName.valueOf( Double.TYPE.getName() ); |
337 | FLOAT = JavaTypeName.valueOf( Float.class.getName() ); |
338 | FLOAT_TYPE = JavaTypeName.valueOf( Float.TYPE.getName() ); |
339 | INTEGER = JavaTypeName.valueOf( Integer.class.getName() ); |
340 | INTEGER_TYPE = JavaTypeName.valueOf( Integer.TYPE.getName() ); |
341 | LONG = JavaTypeName.valueOf( Long.class.getName() ); |
342 | LONG_TYPE = JavaTypeName.valueOf( Long.TYPE.getName() ); |
343 | SHORT = JavaTypeName.valueOf( Short.class.getName() ); |
344 | SHORT_TYPE = JavaTypeName.valueOf( Short.TYPE.getName() ); |
345 | } |
346 | |
347 | /** Creates a new {@code JavaTypeName} instance. */ |
348 | private JavaTypeName() |
349 | { |
350 | super(); |
351 | } |
352 | |
353 | /** |
354 | * Gets the {@code Class} object of the type using a given class loader. |
355 | * |
356 | * @param classLoader The class loader to use for loading the {@code Class} object to return or {@code null}, to |
357 | * load that {@code Class} object using the platform's bootstrap class loader. |
358 | * @param initialize Flag indicating initialization to be performed on the loaded {@code Class} object. |
359 | * |
360 | * @return The {@code Class} object of the type. |
361 | * |
362 | * @throws ClassNotFoundException if the {@code Class} object of the type is not found searching |
363 | * {@code classLoader}. |
364 | * |
365 | * @see Class#forName(java.lang.String, boolean, java.lang.ClassLoader) |
366 | */ |
367 | public Class<?> getClass( final ClassLoader classLoader, final boolean initialize ) throws ClassNotFoundException |
368 | { |
369 | Class<?> javaClass = null; |
370 | |
371 | if ( this.isArray() ) |
372 | { |
373 | javaClass = Class.forName( this.getClassName(), initialize, classLoader ); |
374 | } |
375 | else if ( this.isPrimitive() ) |
376 | { |
377 | if ( BOOLEAN_TYPE.equals( this ) ) |
378 | { |
379 | javaClass = Boolean.TYPE; |
380 | } |
381 | else if ( BYTE_TYPE.equals( this ) ) |
382 | { |
383 | javaClass = Byte.TYPE; |
384 | } |
385 | else if ( CHARACTER_TYPE.equals( this ) ) |
386 | { |
387 | javaClass = Character.TYPE; |
388 | } |
389 | else if ( DOUBLE_TYPE.equals( this ) ) |
390 | { |
391 | javaClass = Double.TYPE; |
392 | } |
393 | else if ( FLOAT_TYPE.equals( this ) ) |
394 | { |
395 | javaClass = Float.TYPE; |
396 | } |
397 | else if ( INTEGER_TYPE.equals( this ) ) |
398 | { |
399 | javaClass = Integer.TYPE; |
400 | } |
401 | else if ( LONG_TYPE.equals( this ) ) |
402 | { |
403 | javaClass = Long.TYPE; |
404 | } |
405 | else if ( SHORT_TYPE.equals( this ) ) |
406 | { |
407 | javaClass = Short.TYPE; |
408 | } |
409 | else |
410 | { |
411 | throw new AssertionError( this ); |
412 | } |
413 | } |
414 | else |
415 | { |
416 | javaClass = Class.forName( this.getClassName(), initialize, classLoader ); |
417 | } |
418 | |
419 | return javaClass; |
420 | } |
421 | |
422 | /** |
423 | * Gets the arguments of the type name. |
424 | * |
425 | * @return An unmodifiable list holding the arguments of the type name. |
426 | */ |
427 | public List<Argument> getArguments() |
428 | { |
429 | if ( this.arguments == null ) |
430 | { |
431 | this.arguments = new ArrayList<Argument>(); |
432 | } |
433 | |
434 | return this.arguments; |
435 | } |
436 | |
437 | /** |
438 | * Gets a flag indicating the type name denotes an array type. |
439 | * |
440 | * @return {@code true}, if the type name denotes an array type; {@code false}, else. |
441 | * |
442 | * @see Class#isArray() |
443 | */ |
444 | public boolean isArray() |
445 | { |
446 | return this.dimension > 0; |
447 | } |
448 | |
449 | /** |
450 | * Gets a flag indicating the type name denotes a primitive type. |
451 | * |
452 | * @return {@code true}, if the type name denotes a primitive type; {@code false}, else. |
453 | * |
454 | * @see Class#isPrimitive() |
455 | */ |
456 | public boolean isPrimitive() |
457 | { |
458 | return this.primitive; |
459 | } |
460 | |
461 | /** |
462 | * Gets a flag indicating the type name denotes a wrapper type of a primitive type. |
463 | * |
464 | * @return {@code true}, if the type name denotes a wrapper type of a primitive type; {@code false}, else. |
465 | */ |
466 | public boolean isUnboxable() |
467 | { |
468 | // The Java Language Specification - Java SE 7 Edition - 5.1.8. Unboxing Conversion |
469 | return BOOLEAN.equals( this ) |
470 | || BYTE.equals( this ) |
471 | || SHORT.equals( this ) |
472 | || CHARACTER.equals( this ) |
473 | || INTEGER.equals( this ) |
474 | || LONG.equals( this ) |
475 | || FLOAT.equals( this ) |
476 | || DOUBLE.equals( this ); |
477 | |
478 | } |
479 | |
480 | /** |
481 | * Gets the type name. |
482 | * |
483 | * @param qualified {@code true}, to return a qualified name; {@code false}, to return a simple name. |
484 | * |
485 | * @return The type name. |
486 | */ |
487 | public String getName( final boolean qualified ) |
488 | { |
489 | return qualified |
490 | ? this.toString() |
491 | : this.getPackageName().length() > 0 |
492 | ? this.toString().substring( this.getPackageName().length() + 1 ) |
493 | : this.toString(); |
494 | |
495 | } |
496 | |
497 | /** |
498 | * Gets the class name of the type name. |
499 | * |
500 | * @return The class name of the type name. |
501 | * |
502 | * @see Class#getName() |
503 | * @see Class#forName(java.lang.String) |
504 | */ |
505 | public String getClassName() |
506 | { |
507 | return this.className; |
508 | } |
509 | |
510 | /** |
511 | * Gets the fully qualified package name of the type name. |
512 | * |
513 | * @return The fully qualified package name of the type name or an empty string, if the type name denotes a type |
514 | * located in an unnamed package. |
515 | * |
516 | * @see #isUnnamedPackage() |
517 | */ |
518 | public String getPackageName() |
519 | { |
520 | return this.packageName; |
521 | } |
522 | |
523 | /** |
524 | * Gets a flag indicating the type name denotes a type located in an unnamed package. |
525 | * |
526 | * @return {@code true}, if the type name denotes a type located in an unnamed package; {@code false}, else. |
527 | * |
528 | * @see #getPackageName() |
529 | */ |
530 | public boolean isUnnamedPackage() |
531 | { |
532 | return this.getPackageName().length() == 0; |
533 | } |
534 | |
535 | /** |
536 | * Gets the fully qualified name of the type name. |
537 | * |
538 | * @return The fully qualified name of the type name. |
539 | */ |
540 | public String getQualifiedName() |
541 | { |
542 | return this.qualifiedName; |
543 | } |
544 | |
545 | /** |
546 | * Gets the simple name of the type name. |
547 | * |
548 | * @return The simple name of the type name. |
549 | */ |
550 | public String getSimpleName() |
551 | { |
552 | return this.simpleName; |
553 | } |
554 | |
555 | /** |
556 | * Gets the type name applying a boxing conversion. |
557 | * |
558 | * @return The converted type name or {@code null}, if the instance cannot be converted. |
559 | * |
560 | * @see #isArray() |
561 | * @see #isPrimitive() |
562 | */ |
563 | public JavaTypeName getBoxedName() |
564 | { |
565 | JavaTypeName boxedName = null; |
566 | |
567 | // The Java Language Specification - Java SE 7 Edition - 5.1.7. Boxing Conversion |
568 | if ( BOOLEAN_TYPE.equals( this ) ) |
569 | { |
570 | boxedName = BOOLEAN; |
571 | } |
572 | else if ( BYTE_TYPE.equals( this ) ) |
573 | { |
574 | boxedName = BYTE; |
575 | } |
576 | else if ( SHORT_TYPE.equals( this ) ) |
577 | { |
578 | boxedName = SHORT; |
579 | } |
580 | else if ( CHARACTER_TYPE.equals( this ) ) |
581 | { |
582 | boxedName = CHARACTER; |
583 | } |
584 | else if ( INTEGER_TYPE.equals( this ) ) |
585 | { |
586 | boxedName = INTEGER; |
587 | } |
588 | else if ( LONG_TYPE.equals( this ) ) |
589 | { |
590 | boxedName = LONG; |
591 | } |
592 | else if ( FLOAT_TYPE.equals( this ) ) |
593 | { |
594 | boxedName = FLOAT; |
595 | } |
596 | else if ( DOUBLE_TYPE.equals( this ) ) |
597 | { |
598 | boxedName = DOUBLE; |
599 | } |
600 | |
601 | return boxedName; |
602 | } |
603 | |
604 | /** |
605 | * Gets the type name applying an unboxing conversion. |
606 | * |
607 | * @return The converted type name or {@code null}, if the instance cannot be converted. |
608 | * |
609 | * @see #isUnboxable() |
610 | */ |
611 | public JavaTypeName getUnboxedName() |
612 | { |
613 | JavaTypeName unboxedName = null; |
614 | |
615 | // The Java Language Specification - Java SE 7 Edition - 5.1.8. Unboxing Conversion |
616 | if ( BOOLEAN.equals( this ) ) |
617 | { |
618 | unboxedName = BOOLEAN_TYPE; |
619 | } |
620 | else if ( BYTE.equals( this ) ) |
621 | { |
622 | unboxedName = BYTE_TYPE; |
623 | } |
624 | else if ( SHORT.equals( this ) ) |
625 | { |
626 | unboxedName = SHORT_TYPE; |
627 | } |
628 | else if ( CHARACTER.equals( this ) ) |
629 | { |
630 | unboxedName = CHARACTER_TYPE; |
631 | } |
632 | else if ( INTEGER.equals( this ) ) |
633 | { |
634 | unboxedName = INTEGER_TYPE; |
635 | } |
636 | else if ( LONG.equals( this ) ) |
637 | { |
638 | unboxedName = LONG_TYPE; |
639 | } |
640 | else if ( FLOAT.equals( this ) ) |
641 | { |
642 | unboxedName = FLOAT_TYPE; |
643 | } |
644 | else if ( DOUBLE.equals( this ) ) |
645 | { |
646 | unboxedName = DOUBLE_TYPE; |
647 | } |
648 | |
649 | return unboxedName; |
650 | } |
651 | |
652 | /** |
653 | * Creates a string representation of the instance. |
654 | * |
655 | * @return A string representation of the instance. |
656 | */ |
657 | @Override |
658 | public String toString() |
659 | { |
660 | if ( this.cachedString == null ) |
661 | { |
662 | final StringBuilder builder = new StringBuilder( this.getQualifiedName() ); |
663 | |
664 | if ( !this.getArguments().isEmpty() ) |
665 | { |
666 | builder.append( "<" ); |
667 | |
668 | for ( int i = 0, s0 = this.getArguments().size(); i < s0; i++ ) |
669 | { |
670 | builder.append( this.getArguments().get( i ) ).append( ", " ); |
671 | } |
672 | |
673 | builder.setLength( builder.length() - 2 ); |
674 | builder.append( ">" ); |
675 | } |
676 | |
677 | if ( this.isArray() ) |
678 | { |
679 | final int idx = this.getQualifiedName().length() - this.dimension * "[]".length(); |
680 | builder.append( builder.substring( idx, this.getQualifiedName().length() ) ); |
681 | builder.delete( idx, this.getQualifiedName().length() ); |
682 | } |
683 | |
684 | this.cachedString = builder.toString(); |
685 | } |
686 | |
687 | return this.cachedString; |
688 | } |
689 | |
690 | /** |
691 | * Gets the hash code value of the object. |
692 | * |
693 | * @return The hash code value of the object. |
694 | */ |
695 | @Override |
696 | public int hashCode() |
697 | { |
698 | return this.toString().hashCode(); |
699 | } |
700 | |
701 | /** |
702 | * Tests whether another object is compile-time equal to this object. |
703 | * |
704 | * @param o The object to compare. |
705 | * |
706 | * @return {@code true}, if {@code o} denotes the same compile-time type name than the object; {@code false}, else. |
707 | */ |
708 | @Override |
709 | public boolean equals( final Object o ) |
710 | { |
711 | boolean equal = o == this; |
712 | |
713 | if ( !equal && o instanceof JavaTypeName ) |
714 | { |
715 | equal = this.toString().equals( o.toString() ); |
716 | } |
717 | |
718 | return equal; |
719 | } |
720 | |
721 | /** |
722 | * Tests whether another object is runtime equal to this object. |
723 | * |
724 | * @param o The object to compare. |
725 | * |
726 | * @return {@code true}, if {@code o} denotes the same runtime type name than the object; {@code false}, else. |
727 | */ |
728 | public boolean runtimeEquals( final Object o ) |
729 | { |
730 | boolean equal = o == this; |
731 | |
732 | if ( !equal && o instanceof JavaTypeName ) |
733 | { |
734 | final JavaTypeName that = (JavaTypeName) o; |
735 | equal = this.getClassName().equals( that.getClassName() ); |
736 | } |
737 | |
738 | return equal; |
739 | } |
740 | |
741 | /** |
742 | * Parses text from the beginning of the given string to produce a {@code JavaTypeName} instance. |
743 | * |
744 | * @param text The text to parse. |
745 | * |
746 | * @return A {@code JavaTypeName} instance corresponding to {@code text}. |
747 | * |
748 | * @throws NullPointerException if {@code text} is {@code null}. |
749 | * @throws ParseException if parsing fails. |
750 | * |
751 | * @see #valueOf(java.lang.String) |
752 | */ |
753 | public static JavaTypeName parse( final String text ) throws ParseException |
754 | { |
755 | if ( text == null ) |
756 | { |
757 | throw new NullPointerException( "text" ); |
758 | } |
759 | |
760 | return parse( text, false ); |
761 | } |
762 | |
763 | /** |
764 | * Parses text from the beginning of the given string to produce a {@code JavaTypeName} instance. |
765 | * <p>Unlike the {@link #parse(String)} method, this method throws an {@code IllegalArgumentException} if parsing |
766 | * fails.</p> |
767 | * |
768 | * @param text The text to parse. |
769 | * |
770 | * @return A {@code JavaTypeName} instance corresponding to {@code text}. |
771 | * |
772 | * @throws NullPointerException if {@code text} is {@code null}. |
773 | * @throws IllegalArgumentException if parsing fails. |
774 | * |
775 | * @see #parse(java.lang.String) |
776 | */ |
777 | public static JavaTypeName valueOf( final String text ) throws IllegalArgumentException |
778 | { |
779 | if ( text == null ) |
780 | { |
781 | throw new NullPointerException( "text" ); |
782 | } |
783 | |
784 | try |
785 | { |
786 | return parse( text, true ); |
787 | } |
788 | catch ( final ParseException e ) |
789 | { |
790 | throw new AssertionError( e ); |
791 | } |
792 | } |
793 | |
794 | private static JavaTypeName parse( final String text, boolean runtimeException ) throws ParseException |
795 | { |
796 | Map<String, JavaTypeName> map = cache == null ? null : cache.get(); |
797 | |
798 | if ( map == null ) |
799 | { |
800 | map = new HashMap<String, JavaTypeName>( 128 ); |
801 | cache = new SoftReference<Map<String, JavaTypeName>>( map ); |
802 | } |
803 | |
804 | synchronized ( map ) |
805 | { |
806 | JavaTypeName javaType = map.get( text ); |
807 | |
808 | if ( javaType == null ) |
809 | { |
810 | javaType = new JavaTypeName(); |
811 | parseType( javaType, text, runtimeException ); |
812 | |
813 | javaType.arguments = javaType.arguments != null |
814 | ? Collections.unmodifiableList( javaType.arguments ) |
815 | : Collections.<Argument>emptyList(); |
816 | |
817 | final String name = javaType.getName( true ); |
818 | final JavaTypeName existingInstance = map.get( name ); |
819 | |
820 | if ( existingInstance != null ) |
821 | { |
822 | map.put( text, existingInstance ); |
823 | javaType = existingInstance; |
824 | } |
825 | else |
826 | { |
827 | map.put( text, javaType ); |
828 | map.put( name, javaType ); |
829 | } |
830 | } |
831 | |
832 | return javaType; |
833 | } |
834 | } |
835 | |
836 | /** |
837 | * JLS - Java SE 7 Edition - Chapter 18. Syntax |
838 | * <pre> |
839 | * Type: |
840 | * BasicType {[]} |
841 | * ReferenceType {[]} |
842 | * </pre> |
843 | * |
844 | * @see #parseReferenceType(org.jomc.model.JavaTypeName.Tokenizer, org.jomc.model.JavaTypeName, boolean, boolean) |
845 | */ |
846 | private static void parseType( final JavaTypeName t, final String text, final boolean runtimeException ) |
847 | throws ParseException |
848 | { |
849 | final Tokenizer tokenizer = new Tokenizer( text, runtimeException ); |
850 | boolean basic_type_or_reference_type_seen = false; |
851 | boolean lpar_seen = false; |
852 | Token token; |
853 | |
854 | while ( ( token = tokenizer.next() ) != null ) |
855 | { |
856 | switch ( token.getKind() ) |
857 | { |
858 | case Tokenizer.TK_BASIC_TYPE: |
859 | if ( basic_type_or_reference_type_seen || !CLASSNAME_ENCODINGS.containsKey( token.getValue() ) ) |
860 | { |
861 | if ( runtimeException ) |
862 | { |
863 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
864 | } |
865 | else |
866 | { |
867 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
868 | } |
869 | } |
870 | basic_type_or_reference_type_seen = true; |
871 | t.className = token.getValue(); |
872 | t.qualifiedName = token.getValue(); |
873 | t.simpleName = token.getValue(); |
874 | t.packageName = ""; |
875 | t.primitive = true; |
876 | break; |
877 | |
878 | case Tokenizer.TK_IDENTIFIER: |
879 | if ( basic_type_or_reference_type_seen ) |
880 | { |
881 | if ( runtimeException ) |
882 | { |
883 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
884 | } |
885 | else |
886 | { |
887 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
888 | } |
889 | } |
890 | basic_type_or_reference_type_seen = true; |
891 | tokenizer.back(); |
892 | parseReferenceType( tokenizer, t, false, runtimeException ); |
893 | break; |
894 | |
895 | case Tokenizer.TK_LPAR: |
896 | if ( !basic_type_or_reference_type_seen || lpar_seen ) |
897 | { |
898 | if ( runtimeException ) |
899 | { |
900 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
901 | } |
902 | else |
903 | { |
904 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
905 | } |
906 | } |
907 | lpar_seen = true; |
908 | break; |
909 | |
910 | case Tokenizer.TK_RPAR: |
911 | if ( !( basic_type_or_reference_type_seen && lpar_seen ) ) |
912 | { |
913 | if ( runtimeException ) |
914 | { |
915 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
916 | } |
917 | else |
918 | { |
919 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
920 | } |
921 | } |
922 | lpar_seen = false; |
923 | t.dimension++; |
924 | t.className = "[" + t.className; |
925 | t.qualifiedName += "[]"; |
926 | t.simpleName += "[]"; |
927 | break; |
928 | |
929 | default: |
930 | if ( runtimeException ) |
931 | { |
932 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
933 | } |
934 | else |
935 | { |
936 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
937 | } |
938 | |
939 | } |
940 | } |
941 | |
942 | if ( !basic_type_or_reference_type_seen || lpar_seen ) |
943 | { |
944 | if ( runtimeException ) |
945 | { |
946 | throw createUnexpectedEndOfInputIllegalArgumentException( tokenizer.input(), tokenizer.length() ); |
947 | } |
948 | else |
949 | { |
950 | throw createUnexpectedEndOfInputParseException( tokenizer.input(), tokenizer.length() ); |
951 | } |
952 | } |
953 | |
954 | if ( t.dimension > 0 ) |
955 | { |
956 | if ( t.primitive ) |
957 | { |
958 | t.className = new StringBuilder( t.className.length() ). |
959 | append( t.className.substring( 0, t.dimension ) ). |
960 | append( CLASSNAME_ENCODINGS.get( t.className.substring( t.dimension ) ) ).toString(); |
961 | |
962 | } |
963 | else |
964 | { |
965 | t.className = new StringBuilder( t.className.length() ). |
966 | append( t.className.substring( 0, t.dimension ) ). |
967 | append( "L" ).append( t.className.substring( t.dimension ) ).append( ";" ).toString(); |
968 | |
969 | } |
970 | } |
971 | |
972 | t.arguments = Collections.unmodifiableList( t.getArguments() ); |
973 | } |
974 | |
975 | /** |
976 | * JLS - Java SE 7 Edition - Chapter 18. Syntax |
977 | * <pre> |
978 | * ReferenceType: |
979 | * Identifier [TypeArguments] { . Identifier [TypeArguments] } |
980 | * </pre> |
981 | * |
982 | * @see #parseTypeArguments(org.jomc.model.JavaTypeName.Tokenizer, org.jomc.model.JavaTypeName, boolean) |
983 | */ |
984 | private static void parseReferenceType( final Tokenizer tokenizer, final JavaTypeName t, |
985 | final boolean in_type_arguments, final boolean runtimeException ) |
986 | throws ParseException |
987 | { |
988 | final StringBuilder classNameBuilder = new StringBuilder( tokenizer.input().length() ); |
989 | final StringBuilder typeNameBuilder = new StringBuilder( tokenizer.input().length() ); |
990 | boolean identifier_seen = false; |
991 | boolean type_arguments_seen = false; |
992 | Token token; |
993 | |
994 | while ( ( token = tokenizer.next() ) != null ) |
995 | { |
996 | switch ( token.getKind() ) |
997 | { |
998 | case Tokenizer.TK_IDENTIFIER: |
999 | if ( identifier_seen || type_arguments_seen ) |
1000 | { |
1001 | if ( runtimeException ) |
1002 | { |
1003 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1004 | } |
1005 | else |
1006 | { |
1007 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1008 | } |
1009 | } |
1010 | identifier_seen = true; |
1011 | type_arguments_seen = false; |
1012 | t.simpleName = token.getValue(); |
1013 | t.packageName = typeNameBuilder.length() > 0 |
1014 | ? typeNameBuilder.substring( 0, typeNameBuilder.length() - 1 ) |
1015 | : ""; |
1016 | |
1017 | classNameBuilder.append( token.getValue() ); |
1018 | typeNameBuilder.append( token.getValue() ); |
1019 | break; |
1020 | |
1021 | case Tokenizer.TK_DOT: |
1022 | if ( !( identifier_seen || type_arguments_seen ) ) |
1023 | { |
1024 | if ( runtimeException ) |
1025 | { |
1026 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1027 | } |
1028 | else |
1029 | { |
1030 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1031 | } |
1032 | } |
1033 | identifier_seen = false; |
1034 | type_arguments_seen = false; |
1035 | classNameBuilder.append( token.getValue() ); |
1036 | typeNameBuilder.append( token.getValue() ); |
1037 | break; |
1038 | |
1039 | case Tokenizer.TK_LT: |
1040 | if ( !identifier_seen ) |
1041 | { |
1042 | if ( runtimeException ) |
1043 | { |
1044 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1045 | } |
1046 | else |
1047 | { |
1048 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1049 | } |
1050 | } |
1051 | identifier_seen = false; |
1052 | type_arguments_seen = true; |
1053 | tokenizer.back(); |
1054 | parseTypeArguments( tokenizer, t, runtimeException ); |
1055 | break; |
1056 | |
1057 | case Tokenizer.TK_LPAR: |
1058 | if ( !( identifier_seen || type_arguments_seen ) || in_type_arguments ) |
1059 | { |
1060 | if ( runtimeException ) |
1061 | { |
1062 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1063 | } |
1064 | else |
1065 | { |
1066 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1067 | } |
1068 | } |
1069 | tokenizer.back(); |
1070 | t.className = classNameBuilder.toString(); |
1071 | t.qualifiedName = typeNameBuilder.toString(); |
1072 | return; |
1073 | |
1074 | case Tokenizer.TK_COMMA: |
1075 | case Tokenizer.TK_GT: |
1076 | if ( !( identifier_seen || type_arguments_seen ) || !in_type_arguments ) |
1077 | { |
1078 | if ( runtimeException ) |
1079 | { |
1080 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1081 | } |
1082 | else |
1083 | { |
1084 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1085 | } |
1086 | } |
1087 | tokenizer.back(); |
1088 | t.className = classNameBuilder.toString(); |
1089 | t.qualifiedName = typeNameBuilder.toString(); |
1090 | return; |
1091 | |
1092 | default: |
1093 | if ( runtimeException ) |
1094 | { |
1095 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1096 | } |
1097 | else |
1098 | { |
1099 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1100 | } |
1101 | |
1102 | } |
1103 | } |
1104 | |
1105 | if ( !( identifier_seen || type_arguments_seen ) ) |
1106 | { |
1107 | if ( runtimeException ) |
1108 | { |
1109 | throw createUnexpectedEndOfInputIllegalArgumentException( tokenizer.input(), tokenizer.length() ); |
1110 | } |
1111 | else |
1112 | { |
1113 | throw createUnexpectedEndOfInputParseException( tokenizer.input(), tokenizer.length() ); |
1114 | } |
1115 | } |
1116 | |
1117 | t.className = classNameBuilder.toString(); |
1118 | t.qualifiedName = typeNameBuilder.toString(); |
1119 | } |
1120 | |
1121 | /** |
1122 | * JLS - Java SE 7 Edition - Chapter 18. Syntax |
1123 | * <pre> |
1124 | * TypeArguments: |
1125 | * < TypeArgument { , TypeArgument } > |
1126 | * </pre> |
1127 | * |
1128 | * @see #parseTypeArgument(org.jomc.model.JavaTypeName.Tokenizer, org.jomc.model.JavaTypeName, boolean) |
1129 | */ |
1130 | private static void parseTypeArguments( final Tokenizer tokenizer, final JavaTypeName t, |
1131 | final boolean runtimeException ) |
1132 | throws ParseException |
1133 | { |
1134 | boolean lt_seen = false; |
1135 | boolean argument_seen = false; |
1136 | Token token; |
1137 | |
1138 | while ( ( token = tokenizer.next() ) != null ) |
1139 | { |
1140 | switch ( token.getKind() ) |
1141 | { |
1142 | case Tokenizer.TK_LT: |
1143 | if ( lt_seen || argument_seen ) |
1144 | { |
1145 | if ( runtimeException ) |
1146 | { |
1147 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1148 | } |
1149 | else |
1150 | { |
1151 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1152 | } |
1153 | } |
1154 | lt_seen = true; |
1155 | argument_seen = false; |
1156 | break; |
1157 | |
1158 | case Tokenizer.TK_GT: |
1159 | if ( !argument_seen ) |
1160 | { |
1161 | if ( runtimeException ) |
1162 | { |
1163 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1164 | } |
1165 | else |
1166 | { |
1167 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1168 | } |
1169 | } |
1170 | return; |
1171 | |
1172 | case Tokenizer.TK_COMMA: |
1173 | if ( !argument_seen ) |
1174 | { |
1175 | if ( runtimeException ) |
1176 | { |
1177 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1178 | } |
1179 | else |
1180 | { |
1181 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1182 | } |
1183 | } |
1184 | argument_seen = false; |
1185 | break; |
1186 | |
1187 | case Tokenizer.TK_IDENTIFIER: |
1188 | if ( !lt_seen || argument_seen ) |
1189 | { |
1190 | if ( runtimeException ) |
1191 | { |
1192 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1193 | } |
1194 | else |
1195 | { |
1196 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1197 | } |
1198 | } |
1199 | argument_seen = true; |
1200 | tokenizer.back(); |
1201 | parseTypeArgument( tokenizer, t, runtimeException ); |
1202 | break; |
1203 | |
1204 | case Tokenizer.TK_QM: |
1205 | if ( !lt_seen || argument_seen ) |
1206 | { |
1207 | if ( runtimeException ) |
1208 | { |
1209 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1210 | } |
1211 | else |
1212 | { |
1213 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1214 | } |
1215 | } |
1216 | argument_seen = true; |
1217 | tokenizer.back(); |
1218 | parseTypeArgument( tokenizer, t, runtimeException ); |
1219 | break; |
1220 | |
1221 | default: |
1222 | if ( runtimeException ) |
1223 | { |
1224 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1225 | } |
1226 | else |
1227 | { |
1228 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1229 | } |
1230 | |
1231 | } |
1232 | } |
1233 | |
1234 | if ( runtimeException ) |
1235 | { |
1236 | throw createUnexpectedEndOfInputIllegalArgumentException( tokenizer.input(), tokenizer.length() ); |
1237 | } |
1238 | else |
1239 | { |
1240 | throw createUnexpectedEndOfInputParseException( tokenizer.input(), tokenizer.length() ); |
1241 | } |
1242 | } |
1243 | |
1244 | /** |
1245 | * <dl><dt>JLS - Java SE 7 Edition - Chapter 18. Syntax</dt> |
1246 | * <dd><pre> |
1247 | * TypeArgument: |
1248 | * ReferenceType |
1249 | * ? [ ( extends | super ) ReferenceType ] |
1250 | * </pre></dd> |
1251 | * <dt>JLS - Java SE 7 Edition - Chapter 4.5.1. Type Arguments and Wildcards</dt> |
1252 | * <dd><pre> |
1253 | * TypeArgument: |
1254 | * ReferenceType |
1255 | * Wildcard |
1256 | * |
1257 | * Wildcard: |
1258 | * ? WildcardBounds<i>opt</i> |
1259 | * |
1260 | * WildcardBounds: |
1261 | * extends ReferenceType |
1262 | * super ReferenceType |
1263 | * </pre></dd></dl> |
1264 | */ |
1265 | private static void parseTypeArgument( final Tokenizer tokenizer, final JavaTypeName t, |
1266 | final boolean runtimeException ) |
1267 | throws ParseException |
1268 | { |
1269 | boolean qm_seen = false; |
1270 | boolean keyword_seen = false; |
1271 | Token token; |
1272 | |
1273 | final Argument argument = new Argument(); |
1274 | t.getArguments().add( argument ); |
1275 | |
1276 | while ( ( token = tokenizer.next() ) != null ) |
1277 | { |
1278 | switch ( token.getKind() ) |
1279 | { |
1280 | case Tokenizer.TK_IDENTIFIER: |
1281 | if ( qm_seen && !keyword_seen ) |
1282 | { |
1283 | if ( runtimeException ) |
1284 | { |
1285 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1286 | } |
1287 | else |
1288 | { |
1289 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1290 | } |
1291 | } |
1292 | tokenizer.back(); |
1293 | argument.typeName = new JavaTypeName(); |
1294 | parseReferenceType( tokenizer, argument.getTypeName(), true, runtimeException ); |
1295 | return; |
1296 | |
1297 | case Tokenizer.TK_QM: |
1298 | if ( qm_seen ) |
1299 | { |
1300 | if ( runtimeException ) |
1301 | { |
1302 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1303 | } |
1304 | else |
1305 | { |
1306 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1307 | } |
1308 | } |
1309 | qm_seen = true; |
1310 | argument.wildcard = true; |
1311 | break; |
1312 | |
1313 | case Tokenizer.TK_KEYWORD: |
1314 | if ( !qm_seen || keyword_seen |
1315 | || !( "extends".equals( token.getValue() ) || "super".equals( token.getValue() ) ) ) |
1316 | { |
1317 | if ( runtimeException ) |
1318 | { |
1319 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1320 | } |
1321 | else |
1322 | { |
1323 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1324 | } |
1325 | } |
1326 | keyword_seen = true; |
1327 | argument.wildcardBounds = token.getValue(); |
1328 | break; |
1329 | |
1330 | case Tokenizer.TK_COMMA: |
1331 | case Tokenizer.TK_GT: |
1332 | if ( !qm_seen || keyword_seen ) |
1333 | { |
1334 | if ( runtimeException ) |
1335 | { |
1336 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1337 | } |
1338 | else |
1339 | { |
1340 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1341 | } |
1342 | } |
1343 | tokenizer.back(); |
1344 | return; |
1345 | |
1346 | default: |
1347 | if ( runtimeException ) |
1348 | { |
1349 | throw createInvalidTokenIllegalArgumentException( tokenizer.input(), token ); |
1350 | } |
1351 | else |
1352 | { |
1353 | throw createInvalidTokenParseException( tokenizer.input(), token ); |
1354 | } |
1355 | |
1356 | } |
1357 | } |
1358 | |
1359 | if ( runtimeException ) |
1360 | { |
1361 | throw createUnexpectedEndOfInputIllegalArgumentException( tokenizer.input(), tokenizer.length() ); |
1362 | } |
1363 | else |
1364 | { |
1365 | throw createUnexpectedEndOfInputParseException( tokenizer.input(), tokenizer.length() ); |
1366 | } |
1367 | } |
1368 | |
1369 | private static ParseException createInvalidTokenParseException( final String input, final Token token ) |
1370 | { |
1371 | if ( token.getValue().length() > 1 ) |
1372 | { |
1373 | return new ParseException( getMessage( "invalidWord", input, token.getValue(), |
1374 | token.getPosition() ), token.getPosition() ); |
1375 | |
1376 | } |
1377 | else |
1378 | { |
1379 | return new ParseException( getMessage( "invalidCharacter", input, token.getValue(), |
1380 | token.getPosition() ), token.getPosition() ); |
1381 | |
1382 | } |
1383 | } |
1384 | |
1385 | private static IllegalArgumentException createInvalidTokenIllegalArgumentException( final String input, |
1386 | final Token token ) |
1387 | { |
1388 | if ( token.getValue().length() > 1 ) |
1389 | { |
1390 | return new IllegalArgumentException( getMessage( "invalidWord", input, token.getValue(), |
1391 | token.getPosition() ) ); |
1392 | |
1393 | } |
1394 | else |
1395 | { |
1396 | return new IllegalArgumentException( getMessage( "invalidCharacter", input, token.getValue(), |
1397 | token.getPosition() ) ); |
1398 | |
1399 | } |
1400 | } |
1401 | |
1402 | private static ParseException createUnexpectedEndOfInputParseException( final String input, |
1403 | final int length ) |
1404 | { |
1405 | return new ParseException( getMessage( "unexpectedEndOfInput", input, length ), length ); |
1406 | } |
1407 | |
1408 | private static IllegalArgumentException createUnexpectedEndOfInputIllegalArgumentException( final String input, |
1409 | final int length ) |
1410 | { |
1411 | return new IllegalArgumentException( getMessage( "unexpectedEndOfInput", input, length ) ); |
1412 | } |
1413 | |
1414 | private static String getMessage( final String key, final Object... args ) |
1415 | { |
1416 | return MessageFormat.format( ResourceBundle.getBundle( |
1417 | JavaTypeName.class.getName().replace( '.', '/' ), Locale.getDefault() ). |
1418 | getString( key ), args ); |
1419 | |
1420 | } |
1421 | |
1422 | private static final class Token |
1423 | { |
1424 | |
1425 | private int kind; |
1426 | |
1427 | private final int position; |
1428 | |
1429 | private final String value; |
1430 | |
1431 | private Token( final int kind, final int position, final String value ) |
1432 | { |
1433 | super(); |
1434 | this.kind = kind; |
1435 | this.position = position; |
1436 | this.value = value; |
1437 | } |
1438 | |
1439 | private int getKind() |
1440 | { |
1441 | return this.kind; |
1442 | } |
1443 | |
1444 | private int getPosition() |
1445 | { |
1446 | return this.position; |
1447 | } |
1448 | |
1449 | private String getValue() |
1450 | { |
1451 | return this.value; |
1452 | } |
1453 | |
1454 | } |
1455 | |
1456 | private static final class Tokenizer |
1457 | { |
1458 | |
1459 | private static final int TK_BASIC_TYPE = 1; |
1460 | |
1461 | private static final int TK_KEYWORD = 2; |
1462 | |
1463 | private static final int TK_LITERAL = 3; |
1464 | |
1465 | private static final int TK_IDENTIFIER = 4; |
1466 | |
1467 | private static final int TK_LPAR = 5; |
1468 | |
1469 | private static final int TK_RPAR = 6; |
1470 | |
1471 | private static final int TK_LT = 7; |
1472 | |
1473 | private static final int TK_GT = 8; |
1474 | |
1475 | private static final int TK_COMMA = 9; |
1476 | |
1477 | private static final int TK_DOT = 10; |
1478 | |
1479 | private static final int TK_QM = 11; |
1480 | |
1481 | private final String input; |
1482 | |
1483 | private int token; |
1484 | |
1485 | private final List<Token> tokens; |
1486 | |
1487 | private int length; |
1488 | |
1489 | private Tokenizer( final String input, final boolean runtimeException ) throws ParseException |
1490 | { |
1491 | super(); |
1492 | this.input = input; |
1493 | this.token = 0; |
1494 | this.tokens = tokenize( input, runtimeException ); |
1495 | |
1496 | if ( !this.tokens.isEmpty() ) |
1497 | { |
1498 | final Token last = this.tokens.get( this.tokens.size() - 1 ); |
1499 | this.length = last.getPosition() + last.getValue().length(); |
1500 | } |
1501 | } |
1502 | |
1503 | private String input() |
1504 | { |
1505 | return this.input; |
1506 | } |
1507 | |
1508 | private Token next() |
1509 | { |
1510 | final int idx = this.token++; |
1511 | return idx < this.tokens.size() ? this.tokens.get( idx ) : null; |
1512 | } |
1513 | |
1514 | private void back() |
1515 | { |
1516 | this.token--; |
1517 | } |
1518 | |
1519 | private int length() |
1520 | { |
1521 | return this.length; |
1522 | } |
1523 | |
1524 | private static List<Token> tokenize( final String input, final boolean runtimeException ) |
1525 | throws ParseException |
1526 | { |
1527 | final List<Token> list = new LinkedList<Token>(); |
1528 | final ParsePosition pos = new ParsePosition( 0 ); |
1529 | |
1530 | for ( Token t = nextToken( pos, input, runtimeException ); |
1531 | t != null; |
1532 | t = nextToken( pos, input, runtimeException ) ) |
1533 | { |
1534 | list.add( t ); |
1535 | } |
1536 | |
1537 | return Collections.unmodifiableList( list ); |
1538 | } |
1539 | |
1540 | private static Token nextToken( final ParsePosition pos, final String str, final boolean runtimeException ) |
1541 | throws ParseException |
1542 | { |
1543 | for ( final int s0 = str.length(); pos.getIndex() < s0; pos.setIndex( pos.getIndex() + 1 ) ) |
1544 | { |
1545 | if ( !Character.isWhitespace( str.charAt( pos.getIndex() ) ) ) |
1546 | { |
1547 | break; |
1548 | } |
1549 | } |
1550 | |
1551 | int idx = pos.getIndex(); |
1552 | Token token = null; |
1553 | |
1554 | if ( idx < str.length() ) |
1555 | { |
1556 | // Check separator characters. |
1557 | switch ( str.charAt( idx ) ) |
1558 | { |
1559 | case ',': |
1560 | token = new Token( TK_COMMA, idx, "," ); |
1561 | pos.setIndex( idx + 1 ); |
1562 | break; |
1563 | case '.': |
1564 | token = new Token( TK_DOT, idx, "." ); |
1565 | pos.setIndex( idx + 1 ); |
1566 | break; |
1567 | case '<': |
1568 | token = new Token( TK_LT, idx, "<" ); |
1569 | pos.setIndex( idx + 1 ); |
1570 | break; |
1571 | case '>': |
1572 | token = new Token( TK_GT, idx, ">" ); |
1573 | pos.setIndex( idx + 1 ); |
1574 | break; |
1575 | case '[': |
1576 | token = new Token( TK_LPAR, idx, "[" ); |
1577 | pos.setIndex( idx + 1 ); |
1578 | break; |
1579 | case ']': |
1580 | token = new Token( TK_RPAR, idx, "]" ); |
1581 | pos.setIndex( idx + 1 ); |
1582 | break; |
1583 | case '?': |
1584 | token = new Token( TK_QM, idx, "?" ); |
1585 | pos.setIndex( idx + 1 ); |
1586 | break; |
1587 | |
1588 | default: |
1589 | token = null; |
1590 | |
1591 | } |
1592 | |
1593 | // Check basic type. |
1594 | if ( token == null ) |
1595 | { |
1596 | for ( final String basicType : JavaLanguage.BASIC_TYPES ) |
1597 | { |
1598 | if ( str.substring( idx ).startsWith( basicType ) ) |
1599 | { |
1600 | idx += basicType.length(); |
1601 | |
1602 | if ( idx >= str.length() |
1603 | || !Character.isJavaIdentifierPart( str.charAt( idx ) ) ) |
1604 | { |
1605 | token = new Token( TK_BASIC_TYPE, pos.getIndex(), basicType ); |
1606 | pos.setIndex( idx ); |
1607 | break; |
1608 | } |
1609 | |
1610 | idx -= basicType.length(); |
1611 | } |
1612 | } |
1613 | } |
1614 | |
1615 | // Check keyword. |
1616 | if ( token == null ) |
1617 | { |
1618 | for ( final String keyword : JavaLanguage.KEYWORDS ) |
1619 | { |
1620 | if ( str.substring( idx ).startsWith( keyword ) ) |
1621 | { |
1622 | idx += keyword.length(); |
1623 | |
1624 | if ( idx >= str.length() |
1625 | || !Character.isJavaIdentifierPart( str.charAt( idx ) ) ) |
1626 | { |
1627 | token = new Token( TK_KEYWORD, pos.getIndex(), keyword ); |
1628 | pos.setIndex( idx ); |
1629 | break; |
1630 | } |
1631 | |
1632 | idx -= keyword.length(); |
1633 | } |
1634 | } |
1635 | } |
1636 | |
1637 | // Check boolean literals. |
1638 | if ( token == null ) |
1639 | { |
1640 | for ( final String literal : JavaLanguage.BOOLEAN_LITERALS ) |
1641 | { |
1642 | if ( str.substring( idx ).startsWith( literal ) ) |
1643 | { |
1644 | idx += literal.length(); |
1645 | |
1646 | if ( idx >= str.length() |
1647 | || !Character.isJavaIdentifierPart( str.charAt( idx ) ) ) |
1648 | { |
1649 | token = new Token( TK_LITERAL, pos.getIndex(), literal ); |
1650 | pos.setIndex( idx ); |
1651 | break; |
1652 | } |
1653 | |
1654 | idx -= literal.length(); |
1655 | } |
1656 | } |
1657 | } |
1658 | |
1659 | // Check null literal. |
1660 | if ( token == null ) |
1661 | { |
1662 | if ( str.substring( idx ).startsWith( JavaLanguage.NULL_LITERAL ) ) |
1663 | { |
1664 | idx += JavaLanguage.NULL_LITERAL.length(); |
1665 | |
1666 | if ( idx >= str.length() |
1667 | || !Character.isJavaIdentifierPart( str.charAt( idx ) ) ) |
1668 | { |
1669 | token = new Token( TK_LITERAL, pos.getIndex(), JavaLanguage.NULL_LITERAL ); |
1670 | pos.setIndex( idx ); |
1671 | } |
1672 | else |
1673 | { |
1674 | idx -= JavaLanguage.NULL_LITERAL.length(); |
1675 | } |
1676 | } |
1677 | } |
1678 | |
1679 | // Check identifier. |
1680 | if ( token == null ) |
1681 | { |
1682 | for ( final int s0 = str.length(); idx < s0; idx++ ) |
1683 | { |
1684 | if ( !( idx == pos.getIndex() |
1685 | ? Character.isJavaIdentifierStart( str.charAt( idx ) ) |
1686 | : Character.isJavaIdentifierPart( str.charAt( idx ) ) ) ) |
1687 | { |
1688 | break; |
1689 | } |
1690 | } |
1691 | |
1692 | if ( idx != pos.getIndex() ) |
1693 | { |
1694 | token = new Token( TK_IDENTIFIER, pos.getIndex(), str.substring( pos.getIndex(), idx ) ); |
1695 | pos.setIndex( idx ); |
1696 | } |
1697 | } |
1698 | |
1699 | if ( token == null ) |
1700 | { |
1701 | final Token invalidToken = |
1702 | new Token( Integer.MIN_VALUE, idx, Character.toString( str.charAt( idx ) ) ); |
1703 | |
1704 | if ( runtimeException ) |
1705 | { |
1706 | throw createInvalidTokenIllegalArgumentException( str, invalidToken ); |
1707 | } |
1708 | else |
1709 | { |
1710 | throw createInvalidTokenParseException( str, invalidToken ); |
1711 | } |
1712 | } |
1713 | } |
1714 | |
1715 | return token; |
1716 | } |
1717 | |
1718 | } |
1719 | |
1720 | } |