/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
* Portions Copyright 2011 MuleSoft, Inc.
*
* 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 org.mule.devkit.model.code;
import org.mule.devkit.model.code.writer.FileCodeWriter;
import org.mule.devkit.model.code.writer.ProgressCodeWriter;
import javax.lang.model.type.TypeMirror;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Root of the code DOM.
* <p/>
* <p/>
* Here's your typical CodeModel application.
* <p/>
* <pre>
* CodeModel cm = new CodeModel();
*
* // generate source code by populating the 'cm' tree.
* cm._class(...);
* ...
*
* // write them out
* cm.build(new File("."));
* </pre>
* <p/>
* <p/>
* Every CodeModel node is always owned by one {@link CodeModel} object
* at any given time (which can be often accesesd by the <tt>owner()</tt> method.)
* <p/>
* 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 CodeModel},
* which gives you a {@link DefinedClass}. Then you invoke a method on it
* to generate a new method, which gives you {@link Method}, and so on.
* <p/>
* There are a few exceptions to this, most notably building {@link Expression}s,
* but generally you work with CodeModel in a top-down fashion.
* <p/>
* Because of this design, most of the CodeModel classes aren't directly instanciable.
* <p/>
* <p/>
* <h2>Where to go from here?</h2>
* <p/>
* Most of the time you'd want to populate new type definitions in a {@link CodeModel}.
* See {@link #_class(String, ClassType)}.
*/
public final class CodeModel {
/**
* The packages that this JCodeWriter contains.
*/
private HashMap<String, Package> packages = new HashMap<String, Package>();
/**
* All JReferencedClasses are pooled here.
*/
private final HashMap<Class<?>, ReferencedClass> refClasses = new HashMap<Class<?>, ReferencedClass>();
private CodeWriter codeWriter;
private OutputStream registryBootstrapStream;
/**
* Obtains a reference to the special "null" type.
*/
public final NullType NULL = new NullType(this);
// primitive types
public final PrimitiveType VOID = new PrimitiveType(this, "void", Void.class);
public final PrimitiveType BOOLEAN = new PrimitiveType(this, "boolean", Boolean.class);
public final PrimitiveType BYTE = new PrimitiveType(this, "byte", Byte.class);
public final PrimitiveType SHORT = new PrimitiveType(this, "short", Short.class);
public final PrimitiveType CHAR = new PrimitiveType(this, "char", Character.class);
public final PrimitiveType INT = new PrimitiveType(this, "int", Integer.class);
public final PrimitiveType FLOAT = new PrimitiveType(this, "float", Float.class);
public final PrimitiveType LONG = new PrimitiveType(this, "long", Long.class);
public final PrimitiveType DOUBLE = new PrimitiveType(this, "double", Double.class);
/**
* 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.FileSystemCaseSensitive") != null) {
return true;
}
} catch (Exception e) {
}
// on Unix, it's case sensitive.
return (File.separatorChar == '/');
}
public CodeModel(CodeWriter codeWriter) {
this.codeWriter = codeWriter;
}
/**
* 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 Package _package(String name) {
Package p = packages.get(name);
if (p == null) {
p = new Package(name, this);
packages.put(name, p);
}
return p;
}
public final Package rootPackage() {
return _package("");
}
/**
* Returns an iterator that walks the packages defined using this code
* writer.
*/
public Iterator<Package> packages() {
return packages.values().iterator();
}
/**
* Creates a new generated class.
*
* @throws ClassAlreadyExistsException When the specified class/interface was already created.
*/
public DefinedClass _class(String fullyqualifiedName) throws ClassAlreadyExistsException {
return _class(fullyqualifiedName, ClassType.CLASS);
}
/**
* Creates a dummy, unknown {@link TypeReference} that represents a given name.
* <p/>
* <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 TypeReference directClass(String name) {
return new DirectClass(this, name);
}
/**
* Creates a new generated class.
*
* @throws ClassAlreadyExistsException When the specified class/interface was already created.
*/
public DefinedClass _class(int mods, String fullyqualifiedName, ClassType t) throws ClassAlreadyExistsException {
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 ClassAlreadyExistsException When the specified class/interface was already created.
*/
public DefinedClass _class(String fullyqualifiedName, ClassType t) throws ClassAlreadyExistsException {
return _class(Modifier.PUBLIC, fullyqualifiedName, t);
}
/**
* Gets a reference to the already created generated class.
*
* @return null
* If the class is not yet created.
* @see Package#_getClass(String)
*/
public DefinedClass _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 {@link #anonymousClass(TypeReference)} instead.
*/
public DefinedClass newAnonymousClass(TypeReference baseType) {
return new AnonymousClass(baseType);
}
/**
* Creates a new anonymous class.
*/
public DefinedClass anonymousClass(TypeReference baseType) {
return new AnonymousClass(baseType);
}
public DefinedClass anonymousClass(Class<?> baseType) {
return anonymousClass(ref(baseType));
}
/**
* Generates Java source code.
* A convenience method for <code>build(destDir,destDir,System.out)</code>.
*
* @param status if non-null, progress indication will be sent to this stream.
* @param destDir source files are generated into this directory.
*/
private void build(File destDir, PrintStream status) throws IOException {
build(destDir, destDir, status);
}
/**
* Generates Java source code.
* A convenience method that calls {@link #build(CodeWriter, CodeWriter)}.
*
* @param status if non-null, progress indication will be sent to this stream.
* @param srcDir Java source files are generated into this directory.
* @param resourceDir Other resource files are generated into this directory.
*/
private 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 <code>build(destDir,System.out)</code>.
*/
private void build(File destDir) throws IOException {
build(destDir, System.out);
}
/**
* A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
*/
private void build(File srcDir, File resourceDir) throws IOException {
build(srcDir, resourceDir, System.out);
}
public void build() throws IOException {
build(this.codeWriter);
}
/**
* A convenience method for <code>build(out,out)</code>.
*/
private void build(CodeWriter out) throws IOException {
build(out, out);
}
/**
* Generates Java source code.
*/
private void build(CodeWriter source, CodeWriter resource) throws IOException {
Package[] pkgs = packages.values().toArray(new Package[packages.size()]);
// avoid concurrent modification exception
for (Package pkg : pkgs) {
pkg.build(source, resource);
}
source.close();
resource.close();
}
/**
* Returns the number of files to be generated if
* {@link #build} is invoked now.
*/
public int countArtifacts() {
int r = 0;
Package[] pkgs = packages.values().toArray(new Package[packages.size()]);
// avoid concurrent modification exception
for (Package pkg : pkgs) {
r += pkg.countArtifacts();
}
return r;
}
public Type ref(TypeMirror typeMirror) {
return ref(typeMirror.toString());
}
/**
* Obtains a reference to an existing class from its Class object.
* <p/>
* <p/>
* The parameter may not be primitive.
*
* @see #_ref(Class) for the version that handles more cases.
*/
public TypeReference ref(Class<?> clazz) {
ReferencedClass jrc = (ReferencedClass) refClasses.get(clazz);
if (jrc == null) {
if (clazz.isPrimitive()) {
throw new IllegalArgumentException(clazz + " is a primitive");
}
if (clazz.isArray()) {
return new ArrayClass(this, _ref(clazz.getComponentType()));
} else {
jrc = new ReferencedClass(clazz);
refClasses.put(clazz, jrc);
}
}
return jrc;
}
public Type _ref(Class<?> c) {
if (c.isPrimitive()) {
return Type.parse(this, c.getName());
} else {
return ref(c);
}
}
/**
* Obtains a reference to an existing class from its fully-qualified
* class name.
* <p/>
* <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 TypeReference}.
*/
public Type ref(String fullyQualifiedClassName) {
try {
return parseType(fullyQualifiedClassName);
} catch (ClassNotFoundException e) {
// fall through
}
return refClass(fullyQualifiedClassName);
}
private TypeReference refClass(String fullyQualifiedClassName) {
try {
// try the context class loader first
return ref(Thread.currentThread().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 DirectClass(this, fullyQualifiedClassName);
}
/**
* Cached for {@link #wildcard()}.
*/
private TypeReference wildcard;
/**
* Gets a {@link TypeReference} representation for "?",
* which is equivalent to "? extends Object".
*/
public TypeReference wildcard() {
if (wildcard == null) {
wildcard = ref(Object.class).wildcard();
}
return wildcard;
}
/**
* Obtains a type object from a type name.
* <p/>
* <p/>
* This method handles primitive types, arrays, and existing {@link Class}es.
*
* @throws ClassNotFoundException If the specified type is not found.
*/
public Type parseType(String name) throws ClassNotFoundException {
// array
if (name.endsWith("[]")) {
return parseType(name.substring(0, name.length() - 2)).array();
}
// try primitive type
try {
return Type.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".)
*
* @return the index of the character next to T.
*/
TypeReference 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));
return refClass("java.lang.Object").wildcard();
}
}
while (idx < s.length()) {
char ch = s.charAt(idx);
if (Character.isJavaIdentifierStart(ch)
|| Character.isJavaIdentifierPart(ch)
|| ch == '.') {
idx++;
} else {
break;
}
}
TypeReference clazz = refClass(s.substring(start, idx));
return parseSuffix(clazz);
}
/**
* Parses additional left-associative suffixes, like type arguments
* and array specifiers.
*/
private TypeReference parseSuffix(TypeReference 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
*/
private void ws() {
while (Character.isWhitespace(s.charAt(idx)) && idx < s.length()) {
idx++;
}
}
/**
* Parses '<T1,T2,...,Tn>'
*
* @return the index of the character next to '>'
*/
private TypeReference parseArguments(TypeReference rawType) throws ClassNotFoundException {
if (s.charAt(idx) != '<') {
throw new IllegalArgumentException();
}
idx++;
List<TypeReference> args = new ArrayList<TypeReference>();
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 TypeReference[args.size()]));
}
if (ch != ',') {
throw new IllegalArgumentException(s);
}
idx++;
}
}
}
public OutputStream getRegistryBootstrapStream() throws IOException {
if( registryBootstrapStream == null ) {
registryBootstrapStream = getCodeWriter().openBinary(null, "META-INF/services/org/mule/config/registry-bootstrap.properties");
}
return registryBootstrapStream;
}
/**
* References to existing classes.
* <p/>
* <p/>
* ReferencedClass is kept in a pool so that they are shared.
* There is one pool for each CodeModel object.
* <p/>
* <p/>
* It is impossible to cache ReferencedClass globally only because
* there is the _package() method, which obtains the owner Package
* object, which is scoped to CodeModel.
*/
private class ReferencedClass extends TypeReference implements Declaration {
private final Class<?> _class;
ReferencedClass(Class<?> _clazz) {
super(CodeModel.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 TypeReference outer() {
Class<?> p = _class.getDeclaringClass();
if (p == null) {
return null;
}
return ref(p);
}
public Package _package() {
String name = fullName();
// this type is array
if (name.indexOf('[') != -1) {
return CodeModel.this._package("");
}
// other normal case
int idx = name.lastIndexOf('.');
if (idx < 0) {
return CodeModel.this._package("");
} else {
return CodeModel.this._package(name.substring(0, idx));
}
}
public TypeReference _extends() {
Class<?> sp = _class.getSuperclass();
if (sp == null) {
if (isInterface()) {
return owner().ref(Object.class);
}
return null;
} else {
return ref(sp);
}
}
public Iterator<TypeReference> _implements() {
final Class<?>[] interfaces = _class.getInterfaces();
return new Iterator<TypeReference>() {
private int idx = 0;
public boolean hasNext() {
return idx < interfaces.length;
}
public TypeReference next() {
return CodeModel.this.ref(interfaces[idx++]);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public boolean isInterface() {
return _class.isInterface();
}
public boolean isAbstract() {
return java.lang.reflect.Modifier.isAbstract(_class.getModifiers());
}
public PrimitiveType getPrimitiveType() {
Class<?> v = boxToPrimitive.get(_class);
if (v != null) {
return parse(CodeModel.this, v.getName());
} else {
return null;
}
}
public boolean isArray() {
return false;
}
public void declare(Formatter f) {
}
public TypeVariable[] typeParams() {
// TODO: does JDK 1.5 reflection provides these information?
return super.typeParams();
}
protected TypeReference substituteParams(TypeVariable[] variables, List<TypeReference> bindings) {
// TODO: does JDK 1.5 reflection provides these information?
return this;
}
}
/**
* Get code writer
*
* @return Code writer
*/
public CodeWriter getCodeWriter() {
return codeWriter;
}
/**
* Conversion from primitive type {@link Class} (such as {@link Integer#TYPE}
* to its boxed type (such as <tt>Integer.class</tt>)
*/
public static final Map<Class<?>, Class<?>> 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);
}
}