/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
* Portions Copyright 2013-2017 Philip Helger + contributors
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.helger.jcodemodel;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import com.helger.jcodemodel.meta.CodeModelBuildingException;
import com.helger.jcodemodel.meta.ErrorTypeFound;
import com.helger.jcodemodel.meta.JCodeModelJavaxLangModelAdapter;
import com.helger.jcodemodel.util.JCSecureLoader;
import com.helger.jcodemodel.util.JCValueEnforcer;
import com.helger.jcodemodel.writer.FileCodeWriter;
import com.helger.jcodemodel.writer.ProgressCodeWriter;
/**
* Root of the code DOM.
* <p>
* Here's your typical CodeModel application.
*
* <pre>
* JCodeModel cm = new JCodeModel();
*
* // generate source code by populating the 'cm' tree.
* cm._class(...);
* ...
*
* // write them out
* cm.build(new File("."));
* </pre>
* <p>
* Every CodeModel node is always owned by one {@link JCodeModel} object at any
* given time (which can be often accessed by the <tt>owner()</tt> method.) As
* such, when you generate Java code, most of the operation works in a top-down
* fashion. For example, you create a class from {@link JCodeModel}, which gives
* you a {@link JDefinedClass}. Then you invoke a method on it to generate a new
* method, which gives you {@link JMethod}, and so on. There are a few
* exceptions to this, most notably building {@link IJExpression}s, but
* generally you work with CodeModel in a top-down fashion. Because of this
* design, most of the CodeModel classes aren't directly instanciable.
* <h2>Where to go from here?</h2>
* <p>
* Most of the time you'd want to populate new type definitions in a
* {@link JCodeModel}. See {@link #_class(String, EClassType)}.
*/
public final class JCodeModel
{
/**
* Conversion from primitive type {@link Class} (such as {@link Integer#TYPE})
* to its boxed type (such as <tt>Integer.class</tt>). It's an unmodifiable
* map.
*/
public static final Map <Class <?>, Class <?>> primitiveToBox;
/**
* The reverse look up for {@link #primitiveToBox}. It's an unmodifiable map.
*/
public static final Map <Class <?>, Class <?>> boxToPrimitive;
static
{
final Map <Class <?>, Class <?>> m1 = new HashMap <> ();
final Map <Class <?>, Class <?>> m2 = new HashMap <> ();
m1.put (Boolean.class, Boolean.TYPE);
m1.put (Byte.class, Byte.TYPE);
m1.put (Character.class, Character.TYPE);
m1.put (Double.class, Double.TYPE);
m1.put (Float.class, Float.TYPE);
m1.put (Integer.class, Integer.TYPE);
m1.put (Long.class, Long.TYPE);
m1.put (Short.class, Short.TYPE);
m1.put (Void.class, Void.TYPE);
// Swap keys and values
for (final Map.Entry <Class <?>, Class <?>> e : m1.entrySet ())
m2.put (e.getValue (), e.getKey ());
boxToPrimitive = Collections.unmodifiableMap (m1);
primitiveToBox = Collections.unmodifiableMap (m2);
}
/** The packages that this JCodeWriter contains. */
private final Map <String, JPackage> m_aPackages = new HashMap <> ();
/** All JReferencedClasses are pooled here. */
private final Map <Class <?>, JReferencedClass> m_aRefClasses = new HashMap <> ();
/** Obtains a reference to the special "null" type. */
public final JNullType NULL = new JNullType (this);
// primitive types
public final JPrimitiveType BOOLEAN = new JPrimitiveType (this, "boolean", Boolean.class, true);
public final JPrimitiveType BYTE = new JPrimitiveType (this, "byte", Byte.class, true);
public final JPrimitiveType CHAR = new JPrimitiveType (this, "char", Character.class, true);
public final JPrimitiveType DOUBLE = new JPrimitiveType (this, "double", Double.class, true);
public final JPrimitiveType FLOAT = new JPrimitiveType (this, "float", Float.class, true);
public final JPrimitiveType INT = new JPrimitiveType (this, "int", Integer.class, true);
public final JPrimitiveType LONG = new JPrimitiveType (this, "long", Long.class, true);
public final JPrimitiveType SHORT = new JPrimitiveType (this, "short", Short.class, true);
public final JPrimitiveType VOID = new JPrimitiveType (this, "void", Void.class, false);
protected static boolean getFileSystemCaseSensitivity ()
{
try
{
// let the system property override, in case the user really
// wants to override.
if (System.getProperty ("com.sun.codemodel.FileSystemCaseSensitive") != null)
return true;
// Add special override to differentiate if Sun implementation is also in
// scope
if (System.getProperty ("com.helger.jcodemodel.FileSystemCaseSensitive") != null)
return true;
}
catch (final Exception e)
{
// Fall through
}
// on Unix, it's case sensitive.
return File.separatorChar == '/';
}
/**
* If the flag is true, we will consider two classes "Foo" and "foo" as a
* collision.
*/
protected final boolean isCaseSensitiveFileSystem = getFileSystemCaseSensitivity ();
/**
* Cached for {@link #wildcard()}.
*/
private AbstractJClass m_aWildcard;
/** The charset used for building the output - null means system default */
private Charset m_aBuildingCharset = null;
/** The newline string to be used. Defaults to system default */
private String m_sBuildingNewLine = AbstractCodeWriter.getDefaultNewLine ();
public JCodeModel ()
{}
/**
* Add a package to the list of packages to be generated
*
* @param name
* Name of the package. Use "" to indicate the root package.
* @return Newly generated package
*/
@Nonnull
public JPackage _package (@Nonnull final String name)
{
JPackage p = m_aPackages.get (name);
if (p == null)
{
p = new JPackage (name, this);
m_aPackages.put (name, p);
}
return p;
}
@Nonnull
public final JPackage rootPackage ()
{
return _package ("");
}
/**
* @return an iterator that walks the packages defined using this code writer.
*/
@Nonnull
public Iterator <JPackage> packages ()
{
return m_aPackages.values ().iterator ();
}
/**
* Creates a new generated class.
*
* @param nMods
* Modifiers to use
* @param sFullyQualifiedClassName
* FQCN
* @param eClassType
* Class type to use (enum/class/interface/annotation)
* @return New {@link JDefinedClass}
* @exception JClassAlreadyExistsException
* When the specified class/interface was already created.
*/
@Nonnull
public JDefinedClass _class (final int nMods,
@Nonnull final String sFullyQualifiedClassName,
@Nonnull final EClassType eClassType) throws JClassAlreadyExistsException
{
final int nIdx = sFullyQualifiedClassName.lastIndexOf ('.');
if (nIdx < 0)
return rootPackage ()._class (nMods, sFullyQualifiedClassName, eClassType);
return _package (sFullyQualifiedClassName.substring (0, nIdx))._class (nMods,
sFullyQualifiedClassName.substring (nIdx +
1),
eClassType);
}
/**
* Creates a new generated class.
*
* @param sFullyQualifiedClassName
* FQCN
* @return New {@link JDefinedClass}
* @exception JClassAlreadyExistsException
* When the specified class/interface was already created.
*/
@Nonnull
public JDefinedClass _class (@Nonnull final String sFullyQualifiedClassName) throws JClassAlreadyExistsException
{
return _class (sFullyQualifiedClassName, EClassType.CLASS);
}
/**
* Creates a new generated class.
*
* @param nMods
* Modifiers to use
* @param sFullyQualifiedClassName
* FQCN
* @return New {@link JDefinedClass}
* @exception JClassAlreadyExistsException
* When the specified class/interface was already created.
*/
@Nonnull
public JDefinedClass _class (final int nMods,
@Nonnull final String sFullyQualifiedClassName) throws JClassAlreadyExistsException
{
return _class (nMods, sFullyQualifiedClassName, EClassType.CLASS);
}
/**
* Creates a new generated class.
*
* @param sFullyQualifiedClassName
* FQCN
* @param eClassType
* Class type to use (enum/class/interface/annotation)
* @return New {@link JDefinedClass}
* @exception JClassAlreadyExistsException
* When the specified class/interface was already created.
*/
@Nonnull
public JDefinedClass _class (@Nonnull final String sFullyQualifiedClassName,
@Nonnull final EClassType eClassType) throws JClassAlreadyExistsException
{
return _class (JMod.PUBLIC, sFullyQualifiedClassName, eClassType);
}
/**
* Creates a dummy, unknown {@link JDirectClass} that represents a given name.
* <br>
* This method is useful when the code generation needs to include the
* user-specified class that may or may not exist, and only thing known about
* it is a class name.
*
* @param sName
* The fully qualified name of the class. When using type parameters
* please use {@link #parseType(String)} instead!
* @return New {@link JDirectClass}
*/
@Nonnull
public JDirectClass directClass (@Nonnull final String sName)
{
return directClass (EClassType.CLASS, sName);
}
/**
* Creates a dummy, unknown {@link JDirectClass} that represents a given name.
* <br>
* This method is useful when the code generation needs to include the
* user-specified class that may or may not exist, and only thing known about
* it is a class name.
*
* @param eClassType
* Class type to use.
* @param sName
* The fully qualified name of the class. When using type parameters
* please use {@link #parseType(String)} instead!
* @return New {@link JDirectClass}
*/
@Nonnull
public JDirectClass directClass (@Nonnull final EClassType eClassType, @Nonnull final String sName)
{
return new JDirectClass (this, null, eClassType, sName);
}
/**
* Creates a dummy, error {@link AbstractJClass} that can only be referenced
* from hidden classes.
* <p>
* This method is useful when the code generation needs to include some error
* class that should never leak into actually written code.
* <p>
* Error-types represents holes or place-holders that can't be filled.
* References to error-classes can be used in hidden class-models. Such
* classes should never be actually written but can be somehow used during
* code generation. Use {@code JCodeModel#buildsErrorTypeRefs} method to test
* if your generated Java-sources contains references to error-types.
* <p>
* You should probably always check generated code with
* {@code JCodeModel#buildsErrorTypeRefs} method if you use any error-types.
* <p>
* Most of error-types methods throws {@code JErrorClassUsedException}
* unchecked exceptions. Be careful and use {@link AbstractJType#isError()
* AbstractJType#isError} method to check for error-types before actually
* using it's methods.
*
* @param sMessage
* some free form text message to identify source of error
* @return New {@link JErrorClass}
* @see JCodeModel#buildsErrorTypeRefs()
* @see JErrorClass
*/
@Nonnull
public JErrorClass errorClass (@Nonnull final String sMessage)
{
return errorClass (sMessage, null);
}
/**
* Creates a dummy, error {@link AbstractJClass} that can only be referenced
* from hidden classes.
* <p>
* This method is useful when the code generation needs to include some error
* class that should never leak into actually written code.
* <p>
* Error-types represents holes or place-holders that can't be filled.
* References to error-classes can be used in hidden class-models. Such
* classes should never be actually written but can be somehow used during
* code generation. Use {@code JCodeModel#buildsErrorTypeRefs} method to test
* if your generated Java-sources contains references to error-types.
* <p>
* You should probably always check generated code with
* {@code JCodeModel#buildsErrorTypeRefs} method if you use any error-types.
* <p>
* Most of error-types methods throws {@code JErrorClassUsedException}
* unchecked exceptions. Be careful and use {@link AbstractJType#isError()
* AbstractJType#isError} method to check for error-types before actually
* using it's methods.
*
* @param sName
* name of missing class if it is known
* @param sMessage
* some free form text message to identify source of error
* @return New {@link JErrorClass}
* @see JCodeModel#buildsErrorTypeRefs()
* @see JErrorClass
*/
@Nonnull
public JErrorClass errorClass (@Nonnull final String sMessage, @Nullable final String sName)
{
return new JErrorClass (this, sMessage, sName);
}
/**
* Check if any error-types leaked into output Java-sources.
*
* @return <code>true</code> if so
* @see JCodeModel#errorClass(String)
*/
public boolean buildsErrorTypeRefs ()
{
final JPackage [] pkgs = m_aPackages.values ().toArray (new JPackage [m_aPackages.size ()]);
// avoid concurrent modification exception
for (final JPackage pkg : pkgs)
{
if (pkg.buildsErrorTypeRefs ())
return true;
}
return false;
}
/**
* Gets a reference to the already created generated class.
*
* @param sFullyQualifiedClassName
* FQCN
* @return <code>null</code> If the class is not yet created.
* @see JPackage#_getClass(String)
*/
@Nullable
public JDefinedClass _getClass (@Nonnull final String sFullyQualifiedClassName)
{
final int nIndex = sFullyQualifiedClassName.lastIndexOf ('.');
if (nIndex < 0)
return rootPackage ()._getClass (sFullyQualifiedClassName);
return _package (sFullyQualifiedClassName.substring (0, nIndex))
._getClass (sFullyQualifiedClassName.substring (nIndex +
1));
}
/**
* Creates a new anonymous class.
*
* @param aBaseClass
* Base class
* @return New {@link JAnonymousClass}
*/
@Nonnull
public JAnonymousClass anonymousClass (@Nonnull final AbstractJClass aBaseClass)
{
return new JAnonymousClass (aBaseClass);
}
/**
* Creates a new anonymous class.
*
* @param aBaseClass
* Base class
* @return New {@link JAnonymousClass}
*/
@Nonnull
public JAnonymousClass anonymousClass (@Nonnull final Class <?> aBaseClass)
{
return anonymousClass (ref (aBaseClass));
}
/**
* @return The default charset used for building. <code>null</code> means
* system default.
*/
@Nullable
public Charset getBuildingCharset ()
{
return m_aBuildingCharset;
}
/**
* Set the charset to be used for emitting files.
*
* @param aCharset
* The charset to be used. May be <code>null</code> to indicate the use
* of the system default.
* @return this for chaining
*/
@Nonnull
public JCodeModel setBuildingCharset (@Nullable final Charset aCharset)
{
m_aBuildingCharset = aCharset;
return this;
}
/**
* @return The newline string to be used. Defaults to system default
*/
public String getBuildingNewLine ()
{
return m_sBuildingNewLine;
}
/**
* Set the new line string to be used for emitting source files.
*
* @param sNewLine
* The new line string to be used. May neither be <code>null</code> nor
* empty.
* @return this for chaining
*/
@Nonnull
public JCodeModel setBuildingNewLine (@Nonnull final String sNewLine)
{
JCValueEnforcer.notEmpty (sNewLine, "NewLine");
m_sBuildingNewLine = sNewLine;
return this;
}
/**
* Generates Java source code. A convenience method for
* <code>build(destDir,destDir,status)</code>.
*
* @param destDir
* source files and resources are generated into this directory.
* @param status
* if non-null, progress indication will be sent to this stream.
* @throws IOException
* on IO error
*/
public void build (@Nonnull final File destDir, @Nullable final PrintStream status) throws IOException
{
build (destDir, destDir, status);
}
/**
* Generates Java source code. A convenience method that calls
* {@link #build(AbstractCodeWriter,AbstractCodeWriter)}.
*
* @param srcDir
* Java source files are generated into this directory.
* @param resourceDir
* Other resource files are generated into this directory.
* @param status
* Progress stream. May be <code>null</code>.
* @throws IOException
* on IO error if non-null, progress indication will be sent to this
* stream.
*/
public void build (@Nonnull final File srcDir,
@Nonnull final File resourceDir,
@Nullable final PrintStream status) throws IOException
{
AbstractCodeWriter res = new FileCodeWriter (resourceDir, m_aBuildingCharset, m_sBuildingNewLine);
AbstractCodeWriter src = new FileCodeWriter (srcDir, m_aBuildingCharset, m_sBuildingNewLine);
if (status != null)
{
src = new ProgressCodeWriter (src, status);
res = new ProgressCodeWriter (res, status);
}
build (src, res);
}
/**
* A convenience method for <code>build(destDir,System.out)</code>.
*
* @param destDir
* source files and resources are generated into this directory.
* @throws IOException
* on IO error
*/
public void build (@Nonnull final File destDir) throws IOException
{
build (destDir, System.out);
}
/**
* A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
*
* @param srcDir
* Java source files are generated into this directory.
* @param resourceDir
* Other resource files are generated into this directory.
* @throws IOException
* on IO error
*/
public void build (@Nonnull final File srcDir, @Nonnull final File resourceDir) throws IOException
{
build (srcDir, resourceDir, System.out);
}
/**
* A convenience method for <code>build(out,out)</code>.
*
* @param out
* Source code and resource writer
* @throws IOException
* on IO error
*/
public void build (@Nonnull final AbstractCodeWriter out) throws IOException
{
build (out, out);
}
/**
* Generates Java source code.
*
* @param source
* Source code writer
* @param resource
* Resource writer
* @throws IOException
* on IO error
*/
public void build (@Nonnull final AbstractCodeWriter source,
@Nonnull final AbstractCodeWriter resource) throws IOException
{
try
{
final JPackage [] pkgs = m_aPackages.values ().toArray (new JPackage [m_aPackages.size ()]);
// avoid concurrent modification exception
for (final JPackage pkg : pkgs)
pkg.build (source, resource);
}
finally
{
source.close ();
resource.close ();
}
}
/**
* @return the number of files to be generated if {@link #build} is invoked
* now.
*/
@Nonnegative
public int countArtifacts ()
{
int r = 0;
final JPackage [] pkgs = m_aPackages.values ().toArray (new JPackage [m_aPackages.size ()]);
// avoid concurrent modification exception
for (final JPackage pkg : pkgs)
r += pkg.countArtifacts ();
return r;
}
/**
* Obtains a reference to an existing class from its Class object.
* <p>
* The parameter may not be primitive.
*
* @param clazz
* Existing class to reference
* @return Singleton reference to this class. Might be a
* {@link JReferencedClass} or a {@link JArrayClass}
* @see #_ref(Class) for the version that handles more cases.
*/
@Nonnull
public AbstractJClass ref (@Nonnull final Class <?> clazz)
{
JReferencedClass aRefClass = m_aRefClasses.get (clazz);
if (aRefClass == null)
{
if (clazz.isPrimitive ())
{
// Cannot return BYTE etc. because the return type does not match
throw new IllegalArgumentException (clazz + " is a primitive");
}
if (clazz.isArray ())
{
final Class <?> aComponentType = clazz.getComponentType ();
// Component type may be a primitive!
return new JArrayClass (this, _ref (aComponentType));
}
aRefClass = new JReferencedClass (this, clazz);
m_aRefClasses.put (clazz, aRefClass);
}
return aRefClass;
}
/**
* Obtains a reference to a processable class from its TypeElement
* description.
* <p>
* Annotation processors can get access of {@link TypeElement} objects during
* annotation processing. These TypeElement objects can be used with
* jcodemodel as a references to classes.
* <p>
* This method result-class definition can never include references to
* "error"-types. Error-types araise during annotation processing when
* something is not fully defined.
* <p>
* You can post-pond annotation processing for later stages of annotation
* processor hoping that all error-types will become defined on some
* annotation processing stage at last. You can catch {@link ErrorTypeFound}
* exception to achieve this.
*
* @param element
* Processable class to reference
* @param elementUtils
* Utility functions to handle Element-objects
* @return Singleton reference to this class.
* @throws ErrorTypeFound
* if some classes are not fully defined during annotation processing.
* @throws CodeModelBuildingException
* In case of an internal error (?)
* @see JCodeModelJavaxLangModelAdapter
* @see #refWithErrorTypes(TypeElement,Elements)
*/
@Nonnull
public JDefinedClass ref (@Nonnull final TypeElement element,
@Nonnull final Elements elementUtils) throws ErrorTypeFound, CodeModelBuildingException
{
final JCodeModelJavaxLangModelAdapter adapter = new JCodeModelJavaxLangModelAdapter (this, elementUtils);
return adapter.getClass (element);
}
/**
* Obtains a reference to a processable class from its TypeElement
* description.
* <p>
* Annotation processors can get access of TypeElement objects during
* annotation processing. These TypeElement objects can be used with
* jcodemodel as a references to classes.
* <p>
* This method result-class definition can include references to
* "error"-types. Error-types araise during annotation processing when
* something is not fully defined.
* <p>
* Sometimes direct treatment of error-types is required. You can use
* {@link AbstractJType#isError()} and
* {@link JCodeModel#buildsErrorTypeRefs()} methods to handle error-types and
* to prevent error-types to leak into generated code.
*
* @param element
* Processable class to reference
* @param elementUtils
* Utility functions to handle Element-objects
* @return Singleton reference to this class.
* @throws CodeModelBuildingException
* In case of an internal error (?)
* @see JCodeModelJavaxLangModelAdapter
* @see #ref(TypeElement, Elements)
* @see JErrorClass
* @see #buildsErrorTypeRefs()
*/
@Nonnull
public JDefinedClass refWithErrorTypes (@Nonnull final TypeElement element,
@Nonnull final Elements elementUtils) throws CodeModelBuildingException
{
final JCodeModelJavaxLangModelAdapter adapter = new JCodeModelJavaxLangModelAdapter (this, elementUtils);
return adapter.getClassWithErrorTypes (element);
}
/**
* Like {@link #ref(Class)} but also handling primitive types!
*
* @param c
* Class to be referenced
* @return primitive or class
*/
@Nonnull
public AbstractJType _ref (@Nonnull final Class <?> c)
{
if (c.isPrimitive ())
return AbstractJType.parse (this, c.getName ());
return ref (c);
}
/**
* Obtains a reference to an existing class from its fully-qualified class
* name. <br>
* First, this method attempts to load the class of the given name. If that
* fails, we assume that the class is derived straight from {@link Object},
* and return a {@link AbstractJClass}.
*
* @param sFullyQualifiedClassName
* FQCN
* @return Singleton reference to this class. Might be a
* {@link JReferencedClass} or a {@link JArrayClass} or a
* {@link JDirectClass}
*/
@Nonnull
public AbstractJClass ref (@Nonnull final String sFullyQualifiedClassName)
{
try
{
// try the context class loader first
return ref (JCSecureLoader.getContextClassLoader ().loadClass (sFullyQualifiedClassName));
}
catch (final ClassNotFoundException e)
{
// fall through
}
// then the default mechanism.
try
{
return ref (Class.forName (sFullyQualifiedClassName));
}
catch (final ClassNotFoundException e)
{
// fall through
}
// assume it's not visible to us.
return new JDirectClass (this, null, EClassType.CLASS, sFullyQualifiedClassName);
}
/**
* @return Singleton {@link AbstractJClass} representation for "?", which is
* equivalent to "? extends Object".
*/
@Nonnull
public AbstractJClass wildcard ()
{
if (m_aWildcard == null)
m_aWildcard = ref (Object.class).wildcard ();
return m_aWildcard;
}
/**
* Obtains a type object from a type name.
* <p>
* This method handles primitive types, arrays, and existing {@link Class}es.
*
* @param name
* Type name to parse
* @return The internal representation of the specified name. Might be a
* {@link JArrayClass}, a {@link JPrimitiveType}, a
* {@link JReferencedClass}, a {@link JNarrowedClass}
*/
@Nonnull
public AbstractJType parseType (@Nonnull final String name)
{
// array
if (name.endsWith ("[]"))
{
// Simply remove trailing "[]"
return parseType (name.substring (0, name.length () - 2)).array ();
}
// try primitive type
try
{
return AbstractJType.parse (this, name);
}
catch (final IllegalArgumentException e)
{
// Not a primitive type
}
// existing class
return new TypeNameParser (name).parseTypeName ();
}
@NotThreadSafe
private final class TypeNameParser
{
private final String m_sTypeName;
private int m_nIdx;
public TypeNameParser (@Nonnull final String s)
{
m_sTypeName = s;
}
/**
* Parses a type name token T (which can be potentially of the form
* T<T1,T2,...>, or "? extends/super T".)
*
* @return The parsed type name
*/
@Nonnull
AbstractJClass parseTypeName ()
{
final int nStart = m_nIdx;
if (m_sTypeName.charAt (m_nIdx) == '?')
{
// wildcard
m_nIdx++;
_skipWs ();
final String head = m_sTypeName.substring (m_nIdx);
if (head.startsWith ("extends"))
{
// 7 == "extends".length
m_nIdx += 7;
_skipWs ();
return parseTypeName ().wildcard ();
}
if (head.startsWith ("super"))
{
// 5 == "super".length
m_nIdx += 5;
_skipWs ();
return parseTypeName ().wildcardSuper ();
}
// not supported
throw new IllegalArgumentException ("only extends/super can follow ?, but found " +
m_sTypeName.substring (m_nIdx));
}
while (m_nIdx < m_sTypeName.length ())
{
final char ch = m_sTypeName.charAt (m_nIdx);
if (Character.isJavaIdentifierStart (ch) || Character.isJavaIdentifierPart (ch) || ch == '.')
m_nIdx++;
else
break;
}
final AbstractJClass clazz = ref (m_sTypeName.substring (nStart, m_nIdx));
return _parseSuffix (clazz);
}
/**
* Parses additional left-associative suffixes, like type arguments and
* array specifiers.
*/
@Nonnull
private AbstractJClass _parseSuffix (@Nonnull final AbstractJClass clazz)
{
if (m_nIdx == m_sTypeName.length ())
{
// hit EOL
return clazz;
}
final char ch = m_sTypeName.charAt (m_nIdx);
if (ch == '<')
return _parseSuffix (_parseArguments (clazz));
if (ch == '[')
{
if (m_sTypeName.charAt (m_nIdx + 1) == ']')
{
m_nIdx += 2;
return _parseSuffix (clazz.array ());
}
throw new IllegalArgumentException ("Expected ']' but found " + m_sTypeName.substring (m_nIdx + 1));
}
return clazz;
}
/**
* Skips whitespaces
*/
private void _skipWs ()
{
while (Character.isWhitespace (m_sTypeName.charAt (m_nIdx)) && m_nIdx < m_sTypeName.length ())
m_nIdx++;
}
/**
* Parses '<T1,T2,...,Tn>'
*
* @return the index of the character next to '>'
*/
@Nonnull
private AbstractJClass _parseArguments (@Nonnull final AbstractJClass rawType)
{
if (m_sTypeName.charAt (m_nIdx) != '<')
throw new IllegalArgumentException ();
m_nIdx++;
final List <AbstractJClass> args = new ArrayList <> ();
while (true)
{
args.add (parseTypeName ());
if (m_nIdx == m_sTypeName.length ())
throw new IllegalArgumentException ("Missing '>' in " + m_sTypeName);
final char ch = m_sTypeName.charAt (m_nIdx);
if (ch == '>')
return rawType.narrow (args);
if (ch != ',')
throw new IllegalArgumentException (m_sTypeName);
m_nIdx++;
}
}
}
}