View Javadoc

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