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