001/*
002 *   Copyright (C) Christian Schulte <cs@schulte.it>, 2012-235
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: JavaTypeName.java 5043 2015-05-27 07:03:39Z schulte $
029 *
030 */
031package org.jomc.model;
032
033import java.io.Serializable;
034import java.lang.ref.Reference;
035import java.lang.ref.SoftReference;
036import java.text.MessageFormat;
037import java.text.ParseException;
038import java.text.ParsePosition;
039import java.util.ArrayList;
040import java.util.Collections;
041import java.util.HashMap;
042import java.util.LinkedList;
043import java.util.List;
044import java.util.Locale;
045import java.util.Map;
046import java.util.ResourceBundle;
047
048/**
049 * Data type of a Java type name.
050 * <p>
051 * This class supports parsing of Java type names as specified in the
052 * Java Language Specification - Java SE 7 Edition - Chapters 3.8ff, 6.5 and 18.
053 * </p>
054 * <p>
055 * <i>Please note that this class will move to package {@code org.jomc.util} in JOMC 2.0.</i>
056 * </p>
057 *
058 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
059 * @version $JOMC: JavaTypeName.java 5043 2015-05-27 07:03:39Z schulte $
060 * @see #parse(java.lang.String)
061 * @see #valueOf(java.lang.String)
062 * @since 1.4
063 */
064public final class JavaTypeName implements Serializable
065{
066
067    /**
068     * Data type of an argument of a parameterized Java type name.
069     *
070     * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
071     * @version $JOMC: JavaTypeName.java 5043 2015-05-27 07:03:39Z schulte $
072     * @since 1.4
073     */
074    public static final class Argument implements Serializable
075    {
076
077        /**
078         * Flag indicating the argument is a wildcard.
079         *
080         * @serial
081         */
082        private boolean wildcard;
083
084        /**
085         * The wildcard bounds of the argument.
086         *
087         * @serial
088         */
089        private String wildcardBounds;
090
091        /**
092         * The type name of the argument.
093         *
094         * @serial
095         */
096        private JavaTypeName typeName;
097
098        /**
099         * 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}