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