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