001/*
002 *   Copyright (C) Christian Schulte, 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 4804 2013-04-22 05:07:33Z 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>This class supports parsing of Java type names as specified in the
051 * Java Language Specification - Java SE 7 Edition - Chapters 3.8ff, 6.5 and 18.</p>
052 *
053 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
054 * @version $JOMC: JavaTypeName.java 4804 2013-04-22 05:07:33Z schulte $
055 * @see #parse(java.lang.String)
056 * @see #valueOf(java.lang.String)
057 * @since 1.4
058 */
059public final class JavaTypeName implements Serializable
060{
061
062    /**
063     * Data type of an argument of a parameterized Java type name.
064     *
065     * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
066     * @version $JOMC: JavaTypeName.java 4804 2013-04-22 05:07:33Z schulte $
067     * @since 1.4
068     */
069    public static final class Argument implements Serializable
070    {
071
072        /**
073         * Flag indicating the argument is a wildcard.
074         * @serial
075         */
076        private boolean wildcard;
077
078        /**
079         * The wildcard bounds of the argument.
080         * @serial
081         */
082        private String wildcardBounds;
083
084        /**
085         * The type name of the argument.
086         * @serial
087         */
088        private JavaTypeName typeName;
089
090        /** Cached string representation. */
091        private transient String cachedString;
092
093        /** Serial version UID for backwards compatibility with 1.4.x object streams. */
094        private static final long serialVersionUID = -6515267147665760819L;
095
096        /** Create a new {@code Argument} instance. */
097        private Argument()
098        {
099            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}