/*
* Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.codemodel.internal;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
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 com.sun.codemodel.internal.writer.FileCodeWriter;
import com.sun.codemodel.internal.writer.ProgressCodeWriter;
Root of the code DOM.
Here's your typical CodeModel application.
JCodeModel cm = new JCodeModel();
// generate source code by populating the 'cm' tree.
cm._class(...);
...
// write them out
cm.build(new File("."));
Every CodeModel node is always owned by one JCodeModel
object at any given time (which can be often accesesd by the owner()
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 JCodeModel
, which gives you a JDefinedClass
. Then you invoke a method on it to generate a new method, which gives you JMethod
, and so on. There are a few exceptions to this, most notably building JExpression
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.
Where to go from here?
Most of the time you'd want to populate new type definitions in a JCodeModel
. See _class(String, ClassType)
.
/**
* 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 accesesd by the {@code owner()} 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 JExpression}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, ClassType)}.
*/
public final class JCodeModel {
The packages that this JCodeWriter contains. /** The packages that this JCodeWriter contains. */
private final HashMap<String,JPackage> packages = new HashMap<>();
Java module in module-info.java
file. /** Java module in {@code module-info.java} file. */
private JModule module;
All JReferencedClasses are pooled here. /** All JReferencedClasses are pooled here. */
private final HashMap<Class<?>,JReferencedClass> refClasses = new HashMap<>();
Obtains a reference to the special "null" type. /** Obtains a reference to the special "null" type. */
public final JNullType NULL = new JNullType(this);
// primitive types
public final JPrimitiveType VOID = new JPrimitiveType(this,"void", Void.class);
public final JPrimitiveType BOOLEAN = new JPrimitiveType(this,"boolean",Boolean.class);
public final JPrimitiveType BYTE = new JPrimitiveType(this,"byte", Byte.class);
public final JPrimitiveType SHORT = new JPrimitiveType(this,"short", Short.class);
public final JPrimitiveType CHAR = new JPrimitiveType(this,"char", Character.class);
public final JPrimitiveType INT = new JPrimitiveType(this,"int", Integer.class);
public final JPrimitiveType FLOAT = new JPrimitiveType(this,"float", Float.class);
public final JPrimitiveType LONG = new JPrimitiveType(this,"long", Long.class);
public final JPrimitiveType DOUBLE = new JPrimitiveType(this,"double", Double.class);
If the flag is true, we will consider two classes "Foo" and "foo"
as a collision.
/**
* If the flag is true, we will consider two classes "Foo" and "foo"
* as a collision.
*/
protected static final boolean isCaseSensitiveFileSystem = getFileSystemCaseSensitivity();
private static boolean getFileSystemCaseSensitivity() {
try {
// let the system property override, in case the user really
// wants to override.
if( System.getProperty("com.sun.codemodel.internal.FileSystemCaseSensitive")!=null )
return true;
} catch( Exception e ) {}
// on Unix, it's case sensitive.
return (File.separatorChar == '/');
}
public JCodeModel() {}
Add a package to the list of packages to be generated.
Params: - name –
Name of the package. Use "" to indicate the root package.
Returns: Newly generated package
/**
* 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
*/
public JPackage _package(String name) {
JPackage p = packages.get(name);
if (p == null) {
p = new JPackage(name, this);
packages.put(name, p);
}
return p;
}
Creates and returns Java module to be generated.
Params: - name – The Name of Java module.
Returns: New Java module.
/**
* Creates and returns Java module to be generated.
* @param name The Name of Java module.
* @return New Java module.
*/
public JModule _moduleInfo(final String name) {
return module = new JModule(name);
}
Returns existing Java module to be generated.
Returns: Java module or null
if Java module was not created yet.
/**
* Returns existing Java module to be generated.
* @return Java module or {@code null} if Java module was not created yet.
*/
public JModule _getModuleInfo() {
return module;
}
Creates Java module instance and adds existing packages with classes to the Java module info.
Used to initialize and build Java module instance with existing packages content.
Params: - name – The Name of Java module.
- requires – Requires directives to add.
Throws: - IllegalStateException – when Java module instance was not initialized.
/**
* Creates Java module instance and adds existing packages with classes to the Java module info.
* Used to initialize and build Java module instance with existing packages content.
* @param name The Name of Java module.
* @param requires Requires directives to add.
* @throws IllegalStateException when Java module instance was not initialized.
*/
public void _prepareModuleInfo(final String name, final String ...requires) {
_moduleInfo(name);
_updateModuleInfo(requires);
}
Adds existing packages with classes to the Java module info.
Java module instance must exist before calling this method.
Used to update Java module instance with existing packages content after it was prepared on client side.
Params: - requires – Requires directives to add.
Throws: - IllegalStateException – when Java module instance was not initialized.
/**
* Adds existing packages with classes to the Java module info.
* Java module instance must exist before calling this method.
* Used to update Java module instance with existing packages content after it was prepared on client side.
* @param requires Requires directives to add.
* @throws IllegalStateException when Java module instance was not initialized.
*/
public void _updateModuleInfo(final String ...requires) {
if (module == null) {
throw new IllegalStateException("Java module instance was not initialized yet.");
}
module._exports(packages.values(), false);
module._requires(requires);
}
public final JPackage rootPackage() {
return _package("");
}
Returns an iterator that walks the packages defined using this code
writer.
/**
* Returns an iterator that walks the packages defined using this code
* writer.
*/
public Iterator<JPackage> packages() {
return packages.values().iterator();
}
Creates a new generated class.
Throws: - JClassAlreadyExistsException –
When the specified class/interface was already created.
/**
* Creates a new generated class.
*
* @exception JClassAlreadyExistsException
* When the specified class/interface was already created.
*/
public JDefinedClass _class(String fullyqualifiedName) throws JClassAlreadyExistsException {
return _class(fullyqualifiedName,ClassType.CLASS);
}
Creates a dummy, unknown JClass
that represents a given name.
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.
/**
* Creates a dummy, unknown {@link JClass} that represents a given name.
*
* <p>
* 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.
*/
public JClass directClass(String name) {
return new JDirectClass(this,name);
}
Creates a new generated class.
Throws: - JClassAlreadyExistsException –
When the specified class/interface was already created.
/**
* Creates a new generated class.
*
* @exception JClassAlreadyExistsException
* When the specified class/interface was already created.
*/
public JDefinedClass _class(int mods, String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
int idx = fullyqualifiedName.lastIndexOf('.');
if( idx<0 ) return rootPackage()._class(fullyqualifiedName);
else
return _package(fullyqualifiedName.substring(0,idx))
._class(mods, fullyqualifiedName.substring(idx+1), t );
}
Creates a new generated class.
Throws: - JClassAlreadyExistsException –
When the specified class/interface was already created.
/**
* Creates a new generated class.
*
* @exception JClassAlreadyExistsException
* When the specified class/interface was already created.
*/
public JDefinedClass _class(String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
return _class( JMod.PUBLIC, fullyqualifiedName, t );
}
Gets a reference to the already created generated class.
See Also: Returns: null
If the class is not yet created.
/**
* Gets a reference to the already created generated class.
*
* @return null
* If the class is not yet created.
* @see JPackage#_getClass(String)
*/
public JDefinedClass _getClass(String fullyQualifiedName) {
int idx = fullyQualifiedName.lastIndexOf('.');
if( idx<0 ) return rootPackage()._getClass(fullyQualifiedName);
else
return _package(fullyQualifiedName.substring(0,idx))
._getClass( fullyQualifiedName.substring(idx+1) );
}
Creates a new anonymous class.
Deprecated: The naming convention doesn't match the rest of the CodeModel. Use anonymousClass(JClass)
instead.
/**
* Creates a new anonymous class.
*
* @deprecated
* The naming convention doesn't match the rest of the CodeModel.
* Use {@link #anonymousClass(JClass)} instead.
*/
public JDefinedClass newAnonymousClass(JClass baseType) {
return new JAnonymousClass(baseType);
}
Creates a new anonymous class.
/**
* Creates a new anonymous class.
*/
public JDefinedClass anonymousClass(JClass baseType) {
return new JAnonymousClass(baseType);
}
public JDefinedClass anonymousClass(Class<?> baseType) {
return anonymousClass(ref(baseType));
}
Generates Java source code.
A convenience method for build(destDir,destDir,System.out)
.
Params: - destDir –
source files are generated into this directory.
- status –
if non-null, progress indication will be sent to this stream.
/**
* Generates Java source code.
* A convenience method for <code>build(destDir,destDir,System.out)</code>.
*
* @param destDir
* source files are generated into this directory.
* @param status
* if non-null, progress indication will be sent to this stream.
*/
public void build( File destDir, PrintStream status ) throws IOException {
build(destDir,destDir,status);
}
Generates Java source code. A convenience method that calls build(CodeWriter, CodeWriter)
. Params: - srcDir –
Java source files are generated into this directory.
- resourceDir –
Other resource files are generated into this directory.
- status –
if non-null, progress indication will be sent to this stream.
/**
* Generates Java source code.
* A convenience method that calls {@link #build(CodeWriter,CodeWriter)}.
*
* @param srcDir
* Java source files are generated into this directory.
* @param resourceDir
* Other resource files are generated into this directory.
* @param status
* if non-null, progress indication will be sent to this stream.
*/
public void build( File srcDir, File resourceDir, PrintStream status ) throws IOException {
CodeWriter src = new FileCodeWriter(srcDir);
CodeWriter res = new FileCodeWriter(resourceDir);
if(status!=null) {
src = new ProgressCodeWriter(src, status );
res = new ProgressCodeWriter(res, status );
}
build(src,res);
}
A convenience method for build(destDir,System.out)
.
/**
* A convenience method for <code>build(destDir,System.out)</code>.
*/
public void build( File destDir ) throws IOException {
build(destDir,System.out);
}
A convenience method for build(srcDir,resourceDir,System.out)
.
/**
* A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
*/
public void build( File srcDir, File resourceDir ) throws IOException {
build(srcDir,resourceDir,System.out);
}
A convenience method for build(out,out)
.
/**
* A convenience method for <code>build(out,out)</code>.
*/
public void build( CodeWriter out ) throws IOException {
build(out,out);
}
Generates Java source code.
/**
* Generates Java source code.
*/
public void build( CodeWriter source, CodeWriter resource ) throws IOException {
JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
// avoid concurrent modification exception
for( JPackage pkg : pkgs ) {
pkg.build(source,resource);
}
if (module != null) {
module.build(source);
}
source.close();
resource.close();
}
Returns the number of files to be generated if build
is invoked now. /**
* Returns the number of files to be generated if
* {@link #build} is invoked now.
*/
public int countArtifacts() {
int r = 0;
JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
// avoid concurrent modification exception
for( JPackage pkg : pkgs )
r += pkg.countArtifacts();
return r;
}
Obtains a reference to an existing class from its Class object.
The parameter may not be primitive.
See Also: - for the version that handles more cases.
/**
* Obtains a reference to an existing class from its Class object.
*
* <p>
* The parameter may not be primitive.
*
* @see #_ref(Class) for the version that handles more cases.
*/
public JClass ref(Class<?> clazz) {
JReferencedClass jrc = (JReferencedClass)refClasses.get(clazz);
if (jrc == null) {
if (clazz.isPrimitive())
throw new IllegalArgumentException(clazz+" is a primitive");
if (clazz.isArray()) {
return new JArrayClass(this, _ref(clazz.getComponentType()));
} else {
jrc = new JReferencedClass(clazz);
refClasses.put(clazz, jrc);
}
}
return jrc;
}
public JType _ref(Class<?> c) {
if(c.isPrimitive())
return JType.parse(this,c.getName());
else
return ref(c);
}
/**
* Obtains a reference to an existing class from its fully-qualified
* class name.
*
* <p>
* 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 JClass}.
*/
public JClass ref(String fullyQualifiedClassName) {
try {
// try the context class loader first
return ref(SecureLoader.getContextClassLoader().loadClass(fullyQualifiedClassName));
} catch (ClassNotFoundException e) {
// fall through
}
// then the default mechanism.
try {
return ref(Class.forName(fullyQualifiedClassName));
} catch (ClassNotFoundException e1) {
// fall through
}
// assume it's not visible to us.
return new JDirectClass(this,fullyQualifiedClassName);
}
Cached for wildcard()
. /**
* Cached for {@link #wildcard()}.
*/
private JClass wildcard;
Gets a JClass
representation for "?", which is equivalent to "? extends Object". /**
* Gets a {@link JClass} representation for "?",
* which is equivalent to "? extends Object".
*/
public JClass wildcard() {
if(wildcard==null)
wildcard = ref(Object.class).wildcard();
return wildcard;
}
Obtains a type object from a type name.
This method handles primitive types, arrays, and existing Class
es.
Throws: - ClassNotFoundException –
If the specified type is not found.
/**
* Obtains a type object from a type name.
*
* <p>
* This method handles primitive types, arrays, and existing {@link Class}es.
*
* @exception ClassNotFoundException
* If the specified type is not found.
*/
public JType parseType(String name) throws ClassNotFoundException {
// array
if(name.endsWith("[]"))
return parseType(name.substring(0,name.length()-2)).array();
// try primitive type
try {
return JType.parse(this,name);
} catch (IllegalArgumentException e) {
;
}
// existing class
return new TypeNameParser(name).parseTypeName();
}
private final class TypeNameParser {
private final String s;
private int idx;
public TypeNameParser(String s) {
this.s = s;
}
Parses a type name token T (which can be potentially of the form Tr&ly;T1,T2,...>,
or "? extends/super T".)
Returns: the index of the character next to T.
/**
* Parses a type name token T (which can be potentially of the form Tr&ly;T1,T2,...>,
* or "? extends/super T".)
*
* @return the index of the character next to T.
*/
JClass parseTypeName() throws ClassNotFoundException {
int start = idx;
if(s.charAt(idx)=='?') {
// wildcard
idx++;
ws();
String head = s.substring(idx);
if(head.startsWith("extends")) {
idx+=7;
ws();
return parseTypeName().wildcard();
} else
if(head.startsWith("super")) {
throw new UnsupportedOperationException("? super T not implemented");
} else {
// not supported
throw new IllegalArgumentException("only extends/super can follow ?, but found "+s.substring(idx));
}
}
while(idx<s.length()) {
char ch = s.charAt(idx);
if(Character.isJavaIdentifierStart(ch)
|| Character.isJavaIdentifierPart(ch)
|| ch=='.')
idx++;
else
break;
}
JClass clazz = ref(s.substring(start,idx));
return parseSuffix(clazz);
}
Parses additional left-associative suffixes, like type arguments
and array specifiers.
/**
* Parses additional left-associative suffixes, like type arguments
* and array specifiers.
*/
private JClass parseSuffix(JClass clazz) throws ClassNotFoundException {
if(idx==s.length())
return clazz; // hit EOL
char ch = s.charAt(idx);
if(ch=='<')
return parseSuffix(parseArguments(clazz));
if(ch=='[') {
if(s.charAt(idx+1)==']') {
idx+=2;
return parseSuffix(clazz.array());
}
throw new IllegalArgumentException("Expected ']' but found "+s.substring(idx+1));
}
return clazz;
}
Skips whitespaces
/**
* Skips whitespaces
*/
private void ws() {
while(Character.isWhitespace(s.charAt(idx)) && idx<s.length())
idx++;
}
Parses '<T1,T2,...,Tn>'
Returns: the index of the character next to '>'
/**
* Parses '<T1,T2,...,Tn>'
*
* @return the index of the character next to '>'
*/
private JClass parseArguments(JClass rawType) throws ClassNotFoundException {
if(s.charAt(idx)!='<')
throw new IllegalArgumentException();
idx++;
List<JClass> args = new ArrayList<JClass>();
while(true) {
args.add(parseTypeName());
if(idx==s.length())
throw new IllegalArgumentException("Missing '>' in "+s);
char ch = s.charAt(idx);
if(ch=='>')
return rawType.narrow(args.toArray(new JClass[args.size()]));
if(ch!=',')
throw new IllegalArgumentException(s);
idx++;
}
}
}
References to existing classes.
JReferencedClass is kept in a pool so that they are shared.
There is one pool for each JCodeModel object.
It is impossible to cache JReferencedClass globally only because
there is the _package() method, which obtains the owner JPackage
object, which is scoped to JCodeModel.
/**
* References to existing classes.
*
* <p>
* JReferencedClass is kept in a pool so that they are shared.
* There is one pool for each JCodeModel object.
*
* <p>
* It is impossible to cache JReferencedClass globally only because
* there is the _package() method, which obtains the owner JPackage
* object, which is scoped to JCodeModel.
*/
private class JReferencedClass extends JClass implements JDeclaration {
private final Class<?> _class;
JReferencedClass(Class<?> _clazz) {
super(JCodeModel.this);
this._class = _clazz;
assert !_class.isArray();
}
public String name() {
return _class.getSimpleName().replace('$','.');
}
public String fullName() {
return _class.getName().replace('$','.');
}
public String binaryName() {
return _class.getName();
}
public JClass outer() {
Class<?> p = _class.getDeclaringClass();
if(p==null) return null;
return ref(p);
}
public JPackage _package() {
String name = fullName();
// this type is array
if (name.indexOf('[') != -1)
return JCodeModel.this._package("");
// other normal case
int idx = name.lastIndexOf('.');
if (idx < 0)
return JCodeModel.this._package("");
else
return JCodeModel.this._package(name.substring(0, idx));
}
public JClass _extends() {
Class<?> sp = _class.getSuperclass();
if (sp == null) {
if(isInterface())
return owner().ref(Object.class);
return null;
} else
return ref(sp);
}
public Iterator<JClass> _implements() {
final Class<?>[] interfaces = _class.getInterfaces();
return new Iterator<JClass>() {
private int idx = 0;
public boolean hasNext() {
return idx < interfaces.length;
}
public JClass next() {
return JCodeModel.this.ref(interfaces[idx++]);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public boolean isInterface() {
return _class.isInterface();
}
public boolean isAbstract() {
return Modifier.isAbstract(_class.getModifiers());
}
public JPrimitiveType getPrimitiveType() {
Class<?> v = boxToPrimitive.get(_class);
if(v!=null)
return JType.parse(JCodeModel.this,v.getName());
else
return null;
}
public boolean isArray() {
return false;
}
public void declare(JFormatter f) {
}
public JTypeVar[] typeParams() {
// TODO: does JDK 1.5 reflection provides these information?
return super.typeParams();
}
protected JClass substituteParams(JTypeVar[] variables, List<JClass> bindings) {
// TODO: does JDK 1.5 reflection provides these information?
return this;
}
}
Conversion from primitive type Class
(such as Integer.TYPE
to its boxed type (such as Integer.class
) /**
* Conversion from primitive type {@link Class} (such as {@link Integer#TYPE}
* to its boxed type (such as {@code Integer.class})
*/
public static final Map<Class<?>,Class<?>> primitiveToBox;
The reverse look up for primitiveToBox
/**
* The reverse look up for {@link #primitiveToBox}
*/
public static final Map<Class<?>,Class<?>> boxToPrimitive;
static {
Map<Class<?>,Class<?>> m1 = new HashMap<Class<?>,Class<?>>();
Map<Class<?>,Class<?>> m2 = new HashMap<Class<?>,Class<?>>();
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);
for (Map.Entry<Class<?>, Class<?>> e : m1.entrySet())
m2.put(e.getValue(),e.getKey());
boxToPrimitive = Collections.unmodifiableMap(m1);
primitiveToBox = Collections.unmodifiableMap(m2);
}
}