/*
* Copyright 2008 Yuxing Huang <felix@webinit.org>
*
* 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 org.webinit.gwt.rebind;
import java.io.PrintWriter;
import org.webinit.gwt.client.ProxyInterface;
import org.webinit.gwt.rebind.ann.EmptyImplementation;
import org.webinit.gwt.rebind.ann.OverrideProxy;
import org.webinit.gwt.rebind.ann.SkipProxy;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
public class InterfaceProxyGenerator extends Generator {
private static String IMPLEMENTATION_SUFFIX = "_ProxiedImpl";
private static String PROXY_SUFFIX = "_WIProxyImpl";
private static String PROXY_OBJECT_PREFIX = "proxyObject_";
private static String VERSION = "WebInit Interface Proxy Generator v0.1";
@Override
public String generate(TreeLogger logger, GeneratorContext context, String typeName)
throws UnableToCompleteException {
// retrieve the type oracle
TypeOracle oracle = context.getTypeOracle();
assert oracle != null;
// get the implementation name from the type name
String packageName;
String implementationName;
if (typeName.lastIndexOf('.') == -1) {
packageName = "";
implementationName = typeName + IMPLEMENTATION_SUFFIX;
}
else {
packageName= typeName.substring(0, typeName.lastIndexOf('.'));
implementationName = typeName.substring(typeName.lastIndexOf('.')+1) + IMPLEMENTATION_SUFFIX;
}
JClassType sourceClass = oracle.findType(typeName);
if (sourceClass == null) {
logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + typeName + "'", null);
throw new UnableToCompleteException();
}
// checking done, get the source writers
ClassSourceFileComposerFactory oocf = new ClassSourceFileComposerFactory(
packageName,
implementationName);
// if the source class is a Class, adds it as a superclass
if (sourceClass.isClass() != null) {
oocf.setSuperclass(typeName);
}
if (sourceClass.isInterface() != null) {
oocf.addImplementedInterface(sourceClass.getQualifiedSourceName());
JClassType[] implementedInterfaces = sourceClass.getImplementedInterfaces();
for (JClassType intf: implementedInterfaces) {
oocf.addImplementedInterface(intf.getQualifiedSourceName());
}
}
oocf.addImplementedInterface(ProxyInterface.class.getCanonicalName());
// go ahead to write source
PrintWriter printWriter = context.tryCreate(logger, packageName, implementationName);
if (null == printWriter) {
return packageName + "." + implementationName;
}
logger.log(TreeLogger.INFO, "Generating " + packageName + "." + implementationName + " for "+typeName);
SourceWriter sourceWriter = oocf.createSourceWriter(printWriter);
sourceWriter.println("public String getWIInterfaceProxyGeneratorVersion() {");
sourceWriter.println("\treturn \"" + VERSION + "\";");
sourceWriter.println("}");
for (JClassType intf: sourceClass.getImplementedInterfaces()) {
JClassType targetClass = oracle.findType(intf.getQualifiedSourceName() + PROXY_SUFFIX);
// the proxy implementation found
if (targetClass != null) {
String proxyObjectName = PROXY_OBJECT_PREFIX + intf.getSimpleSourceName();
writeProxyObject(logger, targetClass.getQualifiedSourceName(), proxyObjectName, sourceWriter);
// write methods
JMethod[] methods = intf.getMethods();
for (JMethod method: methods) {
// do not write out the method if it is a tag method.
if (method.isAnnotationPresent(SkipProxy.class)) {
logger.log(TreeLogger.DEBUG, "Skip " + method.getReadableDeclaration() +
" because it's skipped in the interface.");
continue;
}
// test target class's eligibility
final JMethod targetMethod = getMethod(targetClass, method);
if (targetMethod != null && targetMethod.isAnnotationPresent(SkipProxy.class)) {
logger.log(TreeLogger.DEBUG, "Skip " + method.getReadableDeclaration() +
" because it's skipped in the proxy implementation.");
continue;
}
// test source class's eligibility
final JMethod sourceMethod = getMethod(sourceClass, method);
if (sourceMethod != null && sourceMethod.isAnnotationPresent(OverrideProxy.class)) {
logger.log(TreeLogger.DEBUG, "Skip " + method.getReadableDeclaration() +
" because it's overriden in the source class.");
continue;
}
// notify empty implementation
if (targetMethod != null && targetMethod.isAnnotationPresent(EmptyImplementation.class))
logger.log(TreeLogger.TRACE, "Empty implementation of " + method.getReadableDeclaration() +
" in " + targetClass.getQualifiedSourceName() + ".\n" +
"Please @OverrideProxy in proper classes.");
writeMethod(logger, method, proxyObjectName, sourceWriter);
}
}
}
sourceWriter.println("}");
context.commit(logger, printWriter);
return packageName + "." + implementationName;
}
private JMethod getMethod(JClassType targetClass, JMethod method) {
JParameter[] targetClassParameters = method.getParameters();
JType[] targetClassParameterTypes = new JType[targetClassParameters.length];
for (int index=0; index < targetClassParameters.length; index++) {
targetClassParameterTypes[index] = targetClassParameters[index].getType();
}
try {
JMethod targetMethod = targetClass.getMethod(method.getName(), targetClassParameterTypes);
return targetMethod;
}
catch (NotFoundException nfe) {
return null;
}
}
private void writeProxyObject(TreeLogger log, String proxyType, String proxyObjectName, SourceWriter writer) {
writer.println("private " + proxyType + " " + proxyObjectName + " = new " + proxyType + "();");
}
private void writeMethod(TreeLogger log, JMethod method, String proxyObjectName, SourceWriter writer) {
StringBuffer buf = new StringBuffer();
buf.append("public ");
// return type
JType returnType = method.getReturnType();
buf.append(returnType.getQualifiedSourceName() + " ");
// name
buf.append(method.getName() + "(");
// arguments
JParameter[] args = method.getParameters();
// set the first argument
if (args.length > 0) {
buf.append(args[0].getType().getQualifiedSourceName() + " arg0");
}
// following arguments if available
for (int index = 1; index < args.length; index++) {
JParameter arg = args[index];
buf.append(", " + arg.getType().getQualifiedSourceName() + " arg" + index);
}
// parameter end
buf.append(") ");
JType[] thrs = method.getThrows();
// set the first throw
if (thrs.length > 0) {
buf.append("throws " + thrs[0].getQualifiedSourceName());
}
// following throws
for (int index = 1; index < thrs.length; index ++) {
JType thr = thrs[index];
buf.append(", " + thr.getQualifiedSourceName());
}
// throws end
buf.append(" {\n");
// main statement
if (returnType.isPrimitive() != JPrimitiveType.VOID)
buf.append("return ");
buf.append(proxyObjectName + "." + method.getName() + "(");
if (args.length > 0)
buf.append("arg0");
for (int index = 1; index < args.length; index ++)
buf.append(", arg" + index);
buf.append(");\n");
buf.append("}\n");
// write to writer
writer.print(buf.toString());
}
}