All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.badlogic.gwtref.gen.ReflectionCacheSourceCreator Maven / Gradle / Ivy

There is a newer version: 1.1210.1
Show newest version
/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gwtref.gen;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.typeinfo.*;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class ReflectionCacheSourceCreator {
	private static final List PRIMITIVE_TYPES = Collections.unmodifiableList(Arrays.asList("char", "int",
			"long", "byte", "short", "float", "double", "boolean"));
	final TreeLogger logger;
	final GeneratorContext context;
	final JClassType type;
	final String simpleName;
	final String packageName;
	SourceWriter sw;
	final StringBuilder source = new StringBuilder();
	final List types = new ArrayList();
	final List setterGetterStubs = new ArrayList();
	final List methodStubs = new ArrayList();
	final Map parameterName2ParameterInstantiation = new HashMap();
	final Map typeNames2typeIds = new HashMap();
	int nextTypeId;
	int nextSetterGetterId;
	int nextInvokableId;

	class SetterGetterStub {
		int getter;
		int setter;
		String name;
		String enclosingType;
		String type;
		boolean isStatic;
		boolean isFinal;
		boolean unused;
	}

	class MethodStub {
		String enclosingType;
		String returnType;
		List parameterTypes = new ArrayList();
		String jnsi;
		int methodId;
		boolean isStatic;
		boolean isAbstract;
		boolean isFinal;
		boolean isNative;
		boolean isConstructor;
		boolean isMethod;
		boolean isPublic;
		String name;
		boolean unused;
	}

	public ReflectionCacheSourceCreator (TreeLogger logger, GeneratorContext context, JClassType type) {
		this.logger = logger;
		this.context = context;
		this.type = type;
		this.packageName = type.getPackage().getName();
		this.simpleName = type.getSimpleSourceName() + "Generated";
		logger.log(Type.INFO, type.getQualifiedSourceName());
	}

	public String create () {
		ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, simpleName);
		composer.addImplementedInterface("com.badlogic.gwtref.client.IReflectionCache");
		imports(composer);
		PrintWriter printWriter = context.tryCreate(logger, packageName, simpleName);
		if (printWriter == null) {
			return packageName + "." + simpleName;
		}
		sw = composer.createSourceWriter(context, printWriter);

		generateLookups();

		forNameC();
		newArrayC();

		getArrayLengthT();
		getArrayElementT();
		setArrayElementT();

		getF();
		setF();

		invokeM();

		sw.commit(logger);
		createProxy(type);
		return packageName + "." + simpleName;
	}

	private void createProxy (JClassType type) {
		ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(type.getPackage().getName(),
			type.getSimpleSourceName() + "Proxy");
		PrintWriter printWriter = context.tryCreate(logger, packageName, simpleName);
		if (printWriter == null) {
			return;
		}
		SourceWriter writer = composer.createSourceWriter(context, printWriter);
		writer.commit(logger);
	}

	private void generateLookups () {
		TypeOracle typeOracle = context.getTypeOracle();
		JPackage[] packages = typeOracle.getPackages();

		// gather all types from wanted packages
		for (JPackage p : packages) {
			for (JClassType t : p.getTypes()) {
				gatherTypes(t.getErasedType(), types);
			}
		}

		// gather all types from explicitly requested packages
		try {
			ConfigurationProperty prop = context.getPropertyOracle().getConfigurationProperty("gdx.reflect.include");
			for (String s : prop.getValues()) {
				JClassType type = typeOracle.findType(s);
				if (type != null) gatherTypes(type.getErasedType(), types);
			}
		} catch (BadPropertyValueException e) {
		}

		gatherTypes(typeOracle.findType("java.util.List").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.util.ArrayList").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.util.HashMap").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.util.Map").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.String").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Boolean").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Byte").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Long").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Character").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Short").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Integer").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Float").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.CharSequence").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Double").getErasedType(), types);
		gatherTypes(typeOracle.findType("java.lang.Object").getErasedType(), types);

		// sort the types so the generated output will be stable between runs
		Collections.sort(types, new Comparator() {
			public int compare (JType o1, JType o2) {
				return o1.getQualifiedSourceName().compareTo(o2.getQualifiedSourceName());
			}
		});

		// generate Type lookup generator methods.
		for (JType t : types) {
			p(createTypeGenerator(t));
		}

		// generate reusable parameter objects
		parameterInitialization();

		// sort the stubs so the generated output will be stable between runs
		Collections.sort(setterGetterStubs, new Comparator() {
			@Override
			public int compare (SetterGetterStub o1, SetterGetterStub o2) {
				return new Integer(o1.setter).compareTo(o2.setter);
			}
		});

		// generate field setters/getters
		for (SetterGetterStub stub : setterGetterStubs) {
			String stubSource = generateSetterGetterStub(stub);
			if (stubSource.equals("")) stub.unused = true;
			p(stubSource);
		}

		// sort the stubs so the generated output will be stable between runs
		Collections.sort(methodStubs, new Comparator() {
			@Override
			public int compare (MethodStub o1, MethodStub o2) {
				return new Integer(o1.methodId).compareTo(o2.methodId);
			}
		});

		// generate methods
		for (MethodStub stub : methodStubs) {
			String stubSource = generateMethodStub(stub);
			if (stubSource.equals("")) stub.unused = true;
			p(stubSource);
		}

		logger.log(Type.INFO, types.size() + " types reflected");
	}

	private void out (String message, int nesting) {
		String nestedMsg = "";
		for (int i = 0; i < nesting; i++)
			nestedMsg += "  ";
		logger.log(Type.INFO, nestedMsg);
	}

	int nesting = 0;

	private void gatherTypes (JType type, List types) {
		nesting++;
		// came here from a type that has no super class
		if (type == null) {
			nesting--;
			return;
		}
		// package info
		if (type.getQualifiedSourceName().contains("-")) {
			nesting--;
			return;
		}

		// not visible
		if (!isVisible(type)) {
			nesting--;
			return;
		}

		// filter reflection scope based on configuration in gwt xml module
		boolean keep = false;
		String name = type.getQualifiedSourceName();
		try {
			ConfigurationProperty prop;
			keep |= !name.contains(".");
			prop = context.getPropertyOracle().getConfigurationProperty("gdx.reflect.include");
			for (String s : prop.getValues())
				keep |= name.contains(s);
			prop = context.getPropertyOracle().getConfigurationProperty("gdx.reflect.exclude");
			for (String s : prop.getValues())
				keep &= !name.equals(s);
		} catch (BadPropertyValueException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (!keep) {
			nesting--;
			return;
		}

		// already visited this type
		if (types.contains(type.getErasedType())) {
			nesting--;
			return;
		}
		types.add(type.getErasedType());
		out(type.getErasedType().getQualifiedSourceName(), nesting);

		if (type instanceof JPrimitiveType) {
			// nothing to do for a primitive type
			nesting--;
			return;
		} else {
			// gather fields
			JClassType c = (JClassType)type;
			JField[] fields = c.getFields();
			if (fields != null) {
				for (JField field : fields) {
					gatherTypes(field.getType().getErasedType(), types);
				}
			}

			// gather super types & interfaces
			gatherTypes(c.getSuperclass(), types);
			JClassType[] interfaces = c.getImplementedInterfaces();
			if (interfaces != null) {
				for (JClassType i : interfaces) {
					gatherTypes(i.getErasedType(), types);
				}
			}

			// gather method parameter & return types
			JMethod[] methods = c.getMethods();
			if (methods != null) {
				for (JMethod m : methods) {
					gatherTypes(m.getReturnType().getErasedType(), types);
					if (m.getParameterTypes() != null) {
						for (JType p : m.getParameterTypes()) {
							gatherTypes(p.getErasedType(), types);
						}
					}
				}
			}

			// gather inner classes
			JClassType[] inner = c.getNestedTypes();
			if (inner != null) {
				for (JClassType i : inner) {
					gatherTypes(i.getErasedType(), types);
				}
			}
		}
		nesting--;
	}

	private String generateMethodStub (MethodStub stub) {
		sb.setLength(0);

		if (stub.enclosingType == null) {
			logger.log(Type.INFO, "method '" + stub.name + "' of invisible class is not invokable");
			return "";
		}

		if ((stub.enclosingType.startsWith("java") && !stub.enclosingType.startsWith("java.util")) || stub.enclosingType.contains("google")) {
			logger.log(Type.INFO, "not emitting code for accessing method " + stub.name + " in class '" + stub.enclosingType
				+ ", either in java.* or GWT related class");
			return "";
		}

		if (stub.enclosingType.contains("[]")) {
			logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
				+ "' is not invokable because the class is an array type");
			return "";
		}

		for (int i = 0; i < stub.parameterTypes.size(); i++) {
			String paramType = stub.parameterTypes.get(i);
			if (paramType == null) {
				logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
					+ "' is not invokable because one of its argument types is not visible");
				return "";
			} else if (paramType.startsWith("long") || paramType.contains("java.lang.Long")) {
				logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
					+ " has long parameter, prohibited in JSNI");
				return "";
			} else {
				stub.parameterTypes.set(i, paramType.replace(".class", ""));
			}
		}
		if (stub.returnType == null) {
			logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
				+ "' is not invokable because its return type is not visible");
			return "";
		}
		if (stub.returnType.startsWith("long") || stub.returnType.contains("java.lang.Long")) {
			logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
				+ " has long return type, prohibited in JSNI");
			return "";
		}

		stub.enclosingType = stub.enclosingType.replace(".class", "");
		stub.returnType = stub.returnType.replace(".class", "");

		if (stub.isMethod) {
			boolean isVoid = stub.returnType.equals("void");
			pbn("private native " + (isVoid ? "Object" : stub.returnType) + " m" + stub.methodId + "(");
			if (!stub.isStatic) pbn(stub.enclosingType + " obj" + (stub.parameterTypes.size() > 0 ? ", " : ""));
			int i = 0;
			for (String paramType : stub.parameterTypes) {
				pbn(paramType + " p" + i + (i < stub.parameterTypes.size() - 1 ? "," : ""));
				i++;
			}
			pbn(") /*-{");

			if (!isVoid) pbn("return ");
			if (stub.isStatic)
				pbn("@" + stub.enclosingType + "::" + stub.name + "(" + stub.jnsi + ")(");
			else
				pbn("obj.@" + stub.enclosingType + "::" + stub.name + "(" + stub.jnsi + ")(");

			for (i = 0; i < stub.parameterTypes.size(); i++) {
				pbn("p" + i + (i < stub.parameterTypes.size() - 1 ? ", " : ""));
			}
			pbn(");");
			if (isVoid) pbn("return null;");
			pbn("}-*/;");
		} else {
			pbn("private static " + stub.returnType + " m" + stub.methodId + "(");
			int i = 0;
			for (String paramType : stub.parameterTypes) {
				pbn(paramType + " p" + i + (i < stub.parameterTypes.size() - 1 ? "," : ""));
				i++;
			}
			pbn(") {");
			pbn("return new " + stub.returnType + "(");
			for (i = 0; i < stub.parameterTypes.size(); i++) {
				pbn("p" + i + (i < stub.parameterTypes.size() - 1 ? ", " : ""));
			}
			pbn(")");
			if (!stub.isPublic) {
				// Access non-public constructors through an anonymous class
				pbn("{}");
			}
			pbn(";");

			pbn("}");
		}

		return sb.toString();
	}

	private String generateSetterGetterStub (SetterGetterStub stub) {
		sb.setLength(0);
		if (stub.enclosingType == null || stub.type == null) {
			logger.log(Type.INFO, "field '" + stub.name + "' in class '" + stub.enclosingType + "' is not accessible as its type '"
				+ stub.type + "' is not public");
			return "";
		}
		if (stub.enclosingType.startsWith("java") || stub.enclosingType.contains("google")) {
			logger.log(Type.INFO, "not emitting code for accessing field " + stub.name + " in class '" + stub.enclosingType
				+ ", either in java.* or GWT related class");
			return "";
		}

		if (stub.type.startsWith("long") || stub.type.contains("java.lang.Long")) {
			logger.log(Type.INFO, "not emitting code for accessing field " + stub.name + " in class '" + stub.enclosingType
				+ " as its of type long which can't be used with JSNI");
			return "";
		}

		stub.enclosingType = stub.enclosingType.replace(".class", "");
		stub.type = stub.type.replace(".class", "");

		pbn("private native " + stub.type + " g" + stub.getter + "(" + stub.enclosingType + " obj) /*-{");
		if (stub.isStatic)
			pbn("return @" + stub.enclosingType + "::" + stub.name + ";");
		else
			pbn("return obj.@" + stub.enclosingType + "::" + stub.name + ";");
		pb("}-*/;");

		if (!stub.isFinal) {
			pbn("private native void s" + stub.setter + "(" + stub.enclosingType + " obj, " + stub.type + " value)  /*-{");
			if (stub.isStatic)
				pbn("@" + stub.enclosingType + "::" + stub.name + " = value");
			else
				pbn("obj.@" + stub.enclosingType + "::" + stub.name + " = value;");
			pb("}-*/;");
		}

		return sb.toString();
	}

	private boolean isVisible (JType type) {
		if (type == null) return false;

		if (type instanceof JClassType) {
			if (type instanceof JArrayType) {
				JType componentType = ((JArrayType)type).getComponentType();
				while (componentType instanceof JArrayType) {
					componentType = ((JArrayType)componentType).getComponentType();
				}
				if (componentType instanceof JClassType) {
					return ((JClassType)componentType).isPublic();
				}
			} else {
				return ((JClassType)type).isPublic();
			}
		}
		return true;
	}

	private String createTypeGenerator (JType t) {
		sb.setLength(0);
		int id = nextTypeId++;
		typeNames2typeIds.put(t.getErasedType().getQualifiedSourceName(), id);
		JClassType c = t.isClass();

		String name = t.getErasedType().getQualifiedSourceName();
		String superClass = null;
		if (c != null && (isVisible(c.getSuperclass())))
			superClass = c.getSuperclass().getErasedType().getQualifiedSourceName() + ".class";
		String assignables = null;
		String interfaces = null;

		if (c != null && c.getFlattenedSupertypeHierarchy() != null) {
			assignables = "new HashSet(Arrays.asList(";
			boolean used = false;
			for (JType i : c.getFlattenedSupertypeHierarchy()) {
				if (!isVisible(i) || i.equals(t) || "java.lang.Object".equals(i.getErasedType().getQualifiedSourceName())) continue;
				if (used) assignables += ", ";
				assignables += i.getErasedType().getQualifiedSourceName() + ".class";
				used = true;
			}
			if (used)
				assignables += "))";
			else
				assignables = null;
		}

		if (c == null) {
			// if it's not a class, it may be an interface instead
			c = t.isInterface();
		}
		
		if (c != null && c.getImplementedInterfaces() != null) {
			interfaces = "new HashSet(Arrays.asList(";
			boolean used = false;
			for (JType i : c.getImplementedInterfaces()) {
				if (!isVisible(i) || i.equals(t)) continue;
				if (used) interfaces += ", ";
				interfaces += i.getErasedType().getQualifiedSourceName() + ".class";
				used = true;
			}
			if (used)
				interfaces += "))";
			else
				interfaces = null;
		}
		
		String varName = "c" + id;
		pb("private static Type " + varName + ";");
		pb("private static Type " + varName + "() {");
		pb("if(" + varName + "!=null) return " + varName + ";");
		pb(varName + " = new Type(\"" + name + "\", " + id + ", " + name + ".class, " + superClass + ", " + assignables + ", " + interfaces + ");");

		if( c == null && t.isArray() != null){
			// if it's not a class or an interface, it may be an array instead
			c = t.isArray();
		}
		
		if (c != null) {
			if (c.isEnum() != null) pb(varName + ".isEnum = true;");
			if (c.isArray() != null) {
				pb(varName + ".isArray = true;");
				// For some reason the isStatic() method returns always true for array, which is wrong
				pb(varName + ".isStatic = false;");
				pb(varName + ".isAbstract = true;"); // Arrays are _always_ abstract
			} else {
				pb(varName + ".isStatic = " + c.isStatic() + ";");
				pb(varName + ".isAbstract = " + c.isAbstract() + ";");
			}
			if (c.isMemberType()) pb(varName + ".isMemberClass = true;");
			if (c.isInterface() != null) pb(varName + ".isInterface = true;");
			if (c.isAnnotation() != null) pb(varName + ".isAnnotation = true;");			

			if (c.getFields() != null && c.getFields().length > 0) {
				pb(varName + ".fields = new Field[] {");
				for (JField f : c.getFields()) {
					String enclosingType = getType(c);
					String fieldType = getType(f.getType());
					int setterGetter = nextSetterGetterId++;
					String elementType = getElementTypes(f);
					String annotations = getAnnotations(f.getDeclaredAnnotations());

					pb("    new Field(\"" + f.getName() + "\", " + enclosingType + ", " + fieldType + ", " + f.isFinal() + ", "
						+ f.isDefaultAccess() + ", " + f.isPrivate() + ", " + f.isProtected() + ", " + f.isPublic() + ", "
						+ f.isStatic() + ", " + f.isTransient() + ", " + f.isVolatile() + ", " + setterGetter + ", " + setterGetter
						+ ", " + elementType + ", " + annotations + "), ");

					SetterGetterStub stub = new SetterGetterStub();
					stub.name = f.getName();
					stub.enclosingType = enclosingType;
					stub.type = fieldType;
					stub.isStatic = f.isStatic();
					stub.isFinal = f.isFinal();
					if (enclosingType != null && fieldType != null) {
						stub.getter = setterGetter;
						stub.setter = setterGetter;
					}
					setterGetterStubs.add(stub);
				}
				pb("};");
			}

			createTypeInvokables(c, varName, "Method", c.getMethods());
			if (c.isPublic() && !c.isAbstract() && (c.getEnclosingType() == null || c.isStatic())) {
				createTypeInvokables(c, varName, "Constructor", c.getConstructors());
			} else {
				logger.log(Type.INFO, c.getName() + " can't be instantiated. Constructors not generated");
			}

			if (c.isArray() != null) {
				pb(varName + ".componentType = " + getType(c.isArray().getComponentType()) + ";");
			}
			if (c.isEnum() != null) {
				JEnumConstant[] enumConstants = c.isEnum().getEnumConstants();
				if (enumConstants != null) {
					pb(varName + ".enumConstants = new Object[" + enumConstants.length + "];");
					for (int i = 0; i < enumConstants.length; i++) {
						pb(varName + ".enumConstants[" + i + "] = " + c.getErasedType().getQualifiedSourceName() + "."
							+ enumConstants[i].getName() + ";");
					}
				}
			}

			Annotation[] annotations = c.getDeclaredAnnotations();
			if (annotations != null && annotations.length > 0) {
				pb(varName + ".annotations = " + getAnnotations(annotations) + ";");
			}
		} else {
			pb(varName + ".isAbstract = true;"); // Primitives are _always_ abstract
			pb(varName + ".isPrimitive = true;");
		}

		pb("return " + varName + ";");
		pb("}");
		return sb.toString();
	}

	private void parameterInitialization () {
		p("private static final Parameter[] EMPTY_PARAMETERS = new Parameter[0];");
		for (Map.Entry e : parameterName2ParameterInstantiation.entrySet()) {
			p("private static Parameter " + e.getKey() + ";");
			p("private static Parameter " + e.getKey() + "() {");
			p("    if (" + e.getKey() + " != null) return " + e.getKey() + ";");
			p("    return " + e.getKey() + " = " + e.getValue() + ";");
			p("}");
		}
	}

	private void createTypeInvokables (JClassType c, String varName, String methodType, JAbstractMethod[] methodTypes) {
		if (methodTypes != null && methodTypes.length > 0) {
			pb(varName + "." + methodType.toLowerCase() + "s = new " + methodType + "[] {");
			for (JAbstractMethod m : methodTypes) {
				MethodStub stub = new MethodStub();
				stub.isPublic = m.isPublic();
				stub.enclosingType = getType(c);
				if (m.isMethod() != null) {
					stub.isMethod = true;
					stub.returnType = getType(m.isMethod().getReturnType());
					stub.isStatic = m.isMethod().isStatic();
					stub.isAbstract = m.isMethod().isAbstract();
					stub.isNative = m.isMethod().isAbstract();
					stub.isFinal = m.isMethod().isFinal();
				} else {
					if (m.isPrivate() || m.isDefaultAccess()) {
						logger.log(Type.INFO, "Skipping non-visible constructor for class " + c.getName());
						continue;
					}
					if (m.getEnclosingType().isFinal() && !m.isPublic()) {
						logger.log(Type.INFO, "Skipping non-public constructor for final class" + c.getName());
						continue;
					}
					stub.isConstructor = true;
					stub.returnType = stub.enclosingType;
				}				
				stub.jnsi = "";
				stub.methodId = nextInvokableId++;
				stub.name = m.getName();
				methodStubs.add(stub);

				pbn("    new " + methodType + "(\"" + m.getName() + "\", ");
				pbn(stub.enclosingType + ", ");
				pbn(stub.returnType + ", ");

				if (m.getParameters() != null && m.getParameters().length > 0) {
					pbn("new Parameter[] {");
					for (JParameter p : m.getParameters()) {
						stub.parameterTypes.add(getType(p.getType()));
						stub.jnsi += p.getType().getErasedType().getJNISignature();
						String paramName = (p.getName() + "__" + p.getType().getErasedType().getJNISignature()).replaceAll(
							"[/;\\[\\]]", "_");
						String paramInstantiation = "new Parameter(\"" + p.getName() + "\", " + getType(p.getType()) + ", \""
							+ p.getType().getJNISignature() + "\")";
						parameterName2ParameterInstantiation.put(paramName, paramInstantiation);
						pbn(paramName + "(), ");
					}
					pbn("}, ");
				} else {
					pbn("EMPTY_PARAMETERS,");
				}

				pb(stub.isAbstract + ", " + stub.isFinal + ", " + stub.isStatic + ", " + m.isDefaultAccess() + ", " + m.isPrivate()
					+ ", " + m.isProtected() + ", " + m.isPublic() + ", " + stub.isNative + ", " + m.isVarArgs() + ", "
					+ stub.isMethod + ", " + stub.isConstructor + ", " + stub.methodId + "," + getAnnotations(m.getDeclaredAnnotations()) + "),");
			}
			pb("};");
		}
	}

	private String getElementTypes (JField f) {
		StringBuilder b = new StringBuilder();
		JParameterizedType params = f.getType().isParameterized();
		if (params != null) {
			JClassType[] typeArgs = params.getTypeArgs();
			b.append("new Class[] {");
			for (JClassType typeArg : typeArgs) {
				if (typeArg.isWildcard() != null)
					b.append("null");
				else if (!isVisible(typeArg))
					b.append("null");
				else if (typeArg.isClassOrInterface() != null)
					b.append(typeArg.isClassOrInterface().getQualifiedSourceName()).append(".class");
				else if (typeArg.isParameterized() != null)
					b.append(typeArg.isParameterized().getQualifiedBinaryName()).append(".class");
				else
					b.append("null");
				b.append(", ");
			}
			b.append("}");
			return b.toString();
		}
		return "null";
	}

	private String getAnnotations (Annotation[] annotations) {
		if (annotations != null && annotations.length > 0) {
			int numValidAnnotations = 0;
			final Class[] ignoredAnnotations = {
				Nonnull.class, Nullable.class,
				Deprecated.class, Retention.class,
			};
			StringBuilder b = new StringBuilder();
			b.append("new java.lang.annotation.Annotation[] {");
			for (Annotation annotation : annotations) {
				Class type = annotation.annotationType();
				// skip ignored types, assuming we are not interested in those at runtime
				boolean ignoredType = false;
				for (int i = 0; !ignoredType && i < ignoredAnnotations.length; i++) {
					ignoredType = ignoredAnnotations[i].equals(type);
				}
				if (ignoredType) {
					continue;
				}
				// skip if not annotated with RetentionPolicy.RUNTIME
				Retention retention = type.getAnnotation(Retention.class);
				if (retention == null || retention.value() != RetentionPolicy.RUNTIME) {
					continue;
				}
				numValidAnnotations++;
				// anonymous class
				b.append(" new ").append(type.getCanonicalName()).append("() {");
				// override all methods
				Method[] methods = type.getDeclaredMethods();
				for (Method method : methods) {
					Class returnType = method.getReturnType();
					b.append(" @Override public");
					b.append(" ").append(returnType.getCanonicalName());
					b.append(" ").append(method.getName()).append("() { return");
					if (returnType.isArray()) {
						b.append(" new ").append(returnType.getCanonicalName()).append(" {");
					}
					// invoke the annotation method
					Object invokeResult = null;
					try {
						invokeResult = method.invoke(annotation);
					} catch (IllegalAccessException e) {
						logger.log(Type.ERROR, "Error invoking annotation method.");
					} catch (InvocationTargetException e) {
						logger.log(Type.ERROR, "Error invoking annotation method.");
					}
					// write result as return value
					if (invokeResult != null) {
						if (returnType.equals(String[].class)) {
							// String[]
							for (String s : (String[])invokeResult) {
								b.append(" \"").append(s).append("\",");
							}
						} else if (returnType.equals(String.class)) {
							// String
							b.append(" \"").append((String)invokeResult).append("\"");
						} else if (returnType.equals(Class[].class)) {
							// Class[]
							for (Class c : (Class[])invokeResult) {
								b.append(" ").append(c.getCanonicalName()).append(".class,");
							}
						} else if (returnType.equals(Class.class)) {
							// Class
							b.append(" ").append(((Class)invokeResult).getCanonicalName()).append(".class");
						} else if (returnType.isArray() && returnType.getComponentType().isEnum()) {
							// enum[]
							String enumTypeName = returnType.getComponentType().getCanonicalName();
							int length = Array.getLength(invokeResult);
							for (int i = 0; i < length; i++) {
								Object e = Array.get(invokeResult, i);
								b.append(" ").append(enumTypeName).append(".").append(e.toString()).append(",");
							}
						} else if (returnType.isEnum()) {
							// enum
							b.append(" ").append(returnType.getCanonicalName()).append(".").append(invokeResult.toString());
						} else if (returnType.isArray() && returnType.getComponentType().isPrimitive()) {
							// primitive []
							Class primitiveType = returnType.getComponentType();
							int length = Array.getLength(invokeResult);
							for (int i = 0; i < length; i++) {
								Object n = Array.get(invokeResult, i);
								b.append(" ").append(n.toString());
								if (primitiveType.equals(float.class)) {
									b.append("f");
								}
								b.append(",");
							}
						} else if (returnType.isPrimitive()) {
							// primitive
							b.append(" ").append(invokeResult.toString());
							if (returnType.equals(float.class)) {
								b.append("f");
							}
						} else {
							logger.log(Type.ERROR, "Return type not supported (or not yet implemented).");
						}
					}
					if (returnType.isArray()) {
						b.append(" }");
					}
					b.append("; ");
					b.append("}");
				}
				// must override annotationType()
				b.append(" @Override public Class annotationType() { return ");
				b.append(type.getCanonicalName());
				b.append(".class; }");
				b.append("}, ");
			}
			b.append("}");
			return (numValidAnnotations > 0) ? b.toString() : "null";
		}
		return "null";
	}

	private String getType (JType type) {
		if (!isVisible(type)) return null;
		return type.getErasedType().getQualifiedSourceName() + ".class";
	}

	private void imports (ClassSourceFileComposerFactory composer) {
		composer.addImport("java.security.AccessControlException");
		composer.addImport("java.util.*");
		composer.addImport("com.badlogic.gwtref.client.*");
	}

	private void invokeM () {
		p("public Object invoke(Method m, Object obj, Object[] params) {");
		SwitchedCodeBlock pc = new SwitchedCodeBlock("m.methodId");
		int subN = 0;
		int nDispatch = 0;

		for (MethodStub stub : methodStubs) {
			if (stub.enclosingType == null) continue;
			if (stub.enclosingType.contains("[]")) continue;
			if (stub.returnType == null) continue;
			if (stub.unused) continue;
			boolean paramsOk = true;
			for (String paramType : stub.parameterTypes) {
				if (paramType == null) {
					paramsOk = false;
					break;
				}
			}

			if (!paramsOk) continue;

			sb.setLength(0);
			pbn("return m" + stub.methodId + "(");
			addParameters(stub);
			pbn(");");
			pc.add(stub.methodId, sb.toString());
			nDispatch++;
			if (nDispatch > 1000) {
				pc.print();
				pc = new SwitchedCodeBlock("m.methodId");
				subN++;
				p("   return invoke" + subN + "(m, obj, params);");
				p("}");
				p("public Object invoke" + subN + "(Method m, Object obj, Object[] params) {");
				nDispatch = 0;
			}
		}

		pc.print();
		p("   throw new IllegalArgumentException(\"Missing method-stub \" + m.methodId + \" for method \" + m.name);");
		p("}");
	}

	private void addParameters (MethodStub stub) {
		if (!stub.isStatic && !stub.isConstructor)
			pbn("(" + stub.enclosingType + ")obj" + (stub.parameterTypes.size() > 0 ? "," : ""));
		for (int i = 0; i < stub.parameterTypes.size(); i++) {
			pbn(cast(stub.parameterTypes.get(i), "params[" + i + "]") + (i < stub.parameterTypes.size() - 1 ? ", " : ""));
		}
	}

	private String cast (String paramType, String arg) {
		if (paramType.equals("byte") || paramType.equals("short") || paramType.equals("int") || paramType.equals("long")
			|| paramType.equals("float") || paramType.equals("double")) {
			return "((Number)" + arg + ")." + paramType + "Value()";
		} else if (paramType.equals("boolean")) {
			return "((Boolean)" + arg + ")." + paramType + "Value()";
		} else if (paramType.equals("char")) {
			return "((Character)" + arg + ")." + paramType + "Value()";
		} else {
			return "((" + paramType + ")" + arg + ")";
		}
	}

	private void setF () {
		p("public void set(Field field, Object obj, Object value) throws IllegalAccessException {");
		SwitchedCodeBlock pc = new SwitchedCodeBlock("field.setter");
		for (SetterGetterStub stub : setterGetterStubs) {
			if (stub.enclosingType == null || stub.type == null || stub.isFinal || stub.unused) continue;
			pc.add(stub.setter, "s" + stub.setter + "(" + cast(stub.enclosingType, "obj") + ", " + cast(stub.type, "value")
				+ "); return;");
		}
		pc.print();
		p("   throw new IllegalArgumentException(\"Missing setter-stub \" + field.setter + \" for field \" + field.name);");
		p("}");
	}

	private void getF () {
		p("public Object get(Field field, Object obj) throws IllegalAccessException {");
		SwitchedCodeBlock pc = new SwitchedCodeBlock("field.getter");
		for (SetterGetterStub stub : setterGetterStubs) {
			if (stub.enclosingType == null || stub.type == null || stub.unused) continue;
			pc.add(stub.getter, "return g" + stub.getter + "(" + cast(stub.enclosingType, "obj") + ");");
		}
		pc.print();
		p("   throw new IllegalArgumentException(\"Missing getter-stub \" + field.getter + \" for field \" + field.name);");
		p("}");
	}

	private static boolean isInstantiableWithNewOperator (JClassType t) {
		if (!t.isDefaultInstantiable() || t instanceof JArrayType || t instanceof JEnumType) return false;
		try {
			JConstructor constructor = t.getConstructor(new JType[0]);
			return constructor != null && constructor.isPublic();
		} catch (NotFoundException e) {
			return false;
		}
	}

	private void setArrayElementT () {
		p("public void setArrayElement(Type type, Object obj, int i, Object value) {");
		SwitchedCodeBlock pc = new SwitchedCodeBlock("type.id");

		for (String s : PRIMITIVE_TYPES) {
			if (!typeNames2typeIds.containsKey(s + "[]")) continue;
			pc.add(typeNames2typeIds.get(s + "[]"), "((" + s + "[])obj)[i] = " + cast(s, "value") + "; return;");
		}

		pc.print();
		p("	((Object[])obj)[i] = value;");
		p("}");
	}

	private void getArrayElementT () {
		p("public Object getArrayElement(Type type, Object obj, int i) {");
		SwitchedCodeBlock pc = new SwitchedCodeBlock("type.id");

		for (String s : PRIMITIVE_TYPES) {
			if (!typeNames2typeIds.containsKey(s + "[]")) continue;
			pc.add(typeNames2typeIds.get(s + "[]"), "return ((" + s + "[])obj)[i];");
		}

		pc.print();
		p("	return ((Object[])obj)[i];");
		p("}");
	}

	private void getArrayLengthT () {
		p("public int getArrayLength(Type type, Object obj) {");
		SwitchedCodeBlock pc = new SwitchedCodeBlock("type.id");

		for (String s : PRIMITIVE_TYPES) {
			if (!typeNames2typeIds.containsKey(s + "[]")) continue;
			pc.add(typeNames2typeIds.get(s + "[]"), "return ((" + s + "[])obj).length;");
		}

		pc.print();
		p("	return ((Object[])obj).length;");
		p("}");
	}

	private void newArrayC () {
		p("public Object newArray (Type t, int size) {");
		p("    if (t != null) {");
		SwitchedCodeBlock pc = new SwitchedCodeBlock("t.id");
		for (JType type : types) {
			if (type.getQualifiedSourceName().equals("void")) continue;
			if (type.getQualifiedSourceName().endsWith("Void")) continue;
			String arrayType = type.getErasedType().getQualifiedSourceName() + "[size]";
			if (arrayType.contains("[]")) {
				arrayType = type.getErasedType().getQualifiedSourceName();
				arrayType = arrayType.replaceFirst("\\[\\]", "[size]") + "[]";
			}
			pc.add(typeNames2typeIds.get(type.getQualifiedSourceName()), "return new " + arrayType + ";");
		}
		pc.print();
		p("    }");
		p("    throw new RuntimeException(\"Couldn't create array\");");
		p("}");
	}

	private void forNameC () {
		p("public Type forName(String name) {");
		p("    int hashCode = name.hashCode();");
		int i = 0;

		SwitchedCodeBlockByString cb = new SwitchedCodeBlockByString("hashCode", "name");
		for (String typeName : typeNames2typeIds.keySet()) {
			cb.add(typeName, "return c" + typeNames2typeIds.get(typeName) + "();");
			i++;
			if (i % 1000 == 0) {
				cb.print();
				cb = new SwitchedCodeBlockByString("hashCode", "name");
				p("    return forName" + i + "(name, hashCode);");
				p("}");
				p("private Type forName" + i + ("(String name, int hashCode) {"));
			}
		}
		cb.print();
		p("    return null;");
		p("}");
	}

	void p (String line) {
		sw.println(line);
		source.append(line);
		source.append("\n");
	}

	void pn (String line) {
		sw.print(line);
		source.append(line);
	}

	StringBuilder sb = new StringBuilder();

	void pb (String line) {
		sb.append(line);
		sb.append("\n");
	}

	private void pbn (String line) {
		sb.append(line);
	}

	class SwitchedCodeBlock {
		private List blocks = new ArrayList();
		private final String switchStatement;

		SwitchedCodeBlock (String switchStatement) {
			this.switchStatement = switchStatement;
		}

		void add (int key, String codeBlock) {
			KeyedCodeBlock b = new KeyedCodeBlock();
			b.key = key;
			b.codeBlock = codeBlock;
			blocks.add(b);
		}

		void print () {
			if (blocks.isEmpty()) return;

			p("    switch(" + switchStatement + ") {");
			for (KeyedCodeBlock b : blocks) {
				p("    case " + b.key + ": " + b.codeBlock);
			}
			p("}");
		}

		class KeyedCodeBlock {
			int key;
			String codeBlock;
		}
	}

	class SwitchedCodeBlockByString {
		private Map> blocks = new HashMap>();
		private final String switchStatement;
		private final String expectedValue;

		SwitchedCodeBlockByString (String switchStatement, String expectedValue) {
			this.switchStatement = switchStatement;
			this.expectedValue = expectedValue;
		}

		void add (String key, String codeBlock) {
			KeyedCodeBlock b = new KeyedCodeBlock();
			b.key = key;
			b.codeBlock = codeBlock;
			List blockList = blocks.get(key);
			if (blockList == null) {
				blockList = new ArrayList();
				blocks.put(key, blockList);
			}
			blockList.add(b);
		}

		void print () {
			if (blocks.isEmpty()) return;

			p("    switch(" + switchStatement + ") {");
			for (String key : blocks.keySet()) {
				p("    case " + key.hashCode() + ": ");
				for (KeyedCodeBlock block : blocks.get(key)) {
					p("        if(" + expectedValue + ".equals(\"" + block.key + "\"))" + block.codeBlock);
					p("    break;");
				}
			}
			p("}");
		}

		class KeyedCodeBlock {
			String key;
			String codeBlock;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy