blob: bb26a229fac24a51bdcd923a5c90cadda97cfc37 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.google.gwt.requestfactory.rebind;
import com.google.gwt.core.client.GWT;
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.TypeOracle;
import com.google.gwt.editor.client.AutoBean;
import com.google.gwt.editor.client.AutoBeanFactory;
import com.google.gwt.editor.client.AutoBeanFactory.Category;
import com.google.gwt.editor.client.AutoBeanFactory.NoWrap;
import com.google.gwt.requestfactory.client.impl.AbstractRequest;
import com.google.gwt.requestfactory.client.impl.AbstractRequestContext;
import com.google.gwt.requestfactory.client.impl.AbstractRequestFactory;
import com.google.gwt.requestfactory.client.impl.EntityProxyCategory;
import com.google.gwt.requestfactory.client.impl.messages.RequestData;
import com.google.gwt.requestfactory.rebind.model.ContextMethod;
import com.google.gwt.requestfactory.rebind.model.EntityProxyModel;
import com.google.gwt.requestfactory.rebind.model.RequestFactoryModel;
import com.google.gwt.requestfactory.rebind.model.RequestMethod;
import com.google.gwt.requestfactory.rebind.model.RequestMethod.CollectionType;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.ValueCodex;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
/**
* <p>
* <span style="color:red">Experimental API: This class is still under rapid
* development, and is very likely to be deleted. Use it at your own risk.
* </span>
* </p>
* Generates implementations of
* {@link com.google.gwt.requestfactory.shared.RequestFactory RequestFactory}
* and its nested interfaces.
*/
public class RequestFactoryGenerator extends Generator {
private GeneratorContext context;
private TreeLogger logger;
private RequestFactoryModel model;
@Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
this.context = context;
this.logger = logger;
TypeOracle oracle = context.getTypeOracle();
JClassType toGenerate = oracle.findType(typeName).isInterface();
if (toGenerate == null) {
logger.log(TreeLogger.ERROR, typeName + " is not an interface type");
throw new UnableToCompleteException();
}
String packageName = toGenerate.getPackage().getName();
String simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl";
PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
if (pw == null) {
return packageName + "." + simpleSourceName;
}
model = new RequestFactoryModel(logger, toGenerate);
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
packageName, simpleSourceName);
factory.setSuperclass(AbstractRequestFactory.class.getCanonicalName());
factory.addImplementedInterface(typeName);
SourceWriter sw = factory.createSourceWriter(context, pw);
writeAutoBeanFactory(sw);
writeContextMethods(sw);
writeContextImplementations();
writeTypeMap(sw);
sw.commit(logger);
return factory.getCreatedClassName();
}
private void writeAutoBeanFactory(SourceWriter sw) {
// Map in static implementations of EntityProxy methods
sw.println("@%s(%s.class)", Category.class.getCanonicalName(),
EntityProxyCategory.class.getCanonicalName());
// Don't wrap our id type, because it makes code grungy
sw.println("@%s(%s.class)", NoWrap.class.getCanonicalName(),
EntityProxyId.class.getCanonicalName());
sw.println("interface Factory extends %s {",
AutoBeanFactory.class.getCanonicalName());
sw.indent();
for (EntityProxyModel proxy : model.getAllProxyModels()) {
// AutoBean<FooProxy> com_google_FooProxy();
sw.println("%s<%s> %s();", AutoBean.class.getCanonicalName(),
proxy.getQualifiedSourceName(),
proxy.getQualifiedSourceName().replace('.', '_'));
}
sw.outdent();
sw.println("}");
// public static final Factory FACTORY = GWT.create(Factory.class);
sw.println("public static final Factory FACTORY=%s.create(Factory.class);",
GWT.class.getCanonicalName());
// Write public accessor
sw.println("@Override public Factory getAutoBeanFactory() { return FACTORY; }");
}
private void writeContextImplementations() {
for (ContextMethod method : model.getMethods()) {
PrintWriter pw = context.tryCreate(logger, method.getPackageName(),
method.getSimpleSourceName());
if (pw == null) {
// Already generated
continue;
}
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
method.getPackageName(), method.getSimpleSourceName());
factory.setSuperclass(AbstractRequestContext.class.getCanonicalName());
factory.addImplementedInterface(method.getImplementedInterfaceQualifiedSourceName());
SourceWriter sw = factory.createSourceWriter(context, pw);
// Constructor that accepts the parent RequestFactory
sw.println("public %s(%s requestFactory) {super(requestFactory);}",
method.getSimpleSourceName(),
AbstractRequestFactory.class.getCanonicalName());
// Write each Request method
for (RequestMethod request : method.getRequestMethods()) {
JMethod jmethod = request.getDeclarationMethod();
String operation = jmethod.getEnclosingType().getQualifiedBinaryName()
+ "::" + jmethod.getName();
// foo, bar, baz
StringBuilder parameterArray = new StringBuilder();
// final Foo foo, final Bar bar, final Baz baz
StringBuilder parameterDeclaration = new StringBuilder();
if (request.isInstance()) {
// Leave a spot for the using() method to fill in later
parameterArray.append(",null");
}
for (JParameter param : jmethod.getParameters()) {
parameterArray.append(",").append(param.getName());
parameterDeclaration.append(",final ").append(
param.getType().getParameterizedQualifiedSourceName()).append(" ").append(
param.getName());
}
if (parameterArray.length() > 0) {
parameterArray.deleteCharAt(0);
}
if (parameterDeclaration.length() > 0) {
parameterDeclaration.deleteCharAt(0);
}
// public Request<Foo> doFoo(final Foo foo) {
sw.println("public %s %s(%s) {",
jmethod.getReturnType().getParameterizedQualifiedSourceName(),
jmethod.getName(), parameterDeclaration);
sw.indent();
// Have to cover the old Request sub-interfaces
// TODO: ProxyListRequest et al. be removed?
// class X extends AbstractRequest<Return> implements ReturnType {
sw.println("class X extends %s<%s> implements %s {",
AbstractRequest.class.getCanonicalName(),
request.getDataType().getParameterizedQualifiedSourceName(),
jmethod.getReturnType().getParameterizedQualifiedSourceName());
sw.indent();
// public X() { super(FooRequestContext.this); }
sw.println("public X() { super(%s.this);}",
method.getSimpleSourceName());
// This could also be gotten rid of by having only Request /
// InstanceRequest
sw.println("@Override public X with(String... paths) {super.with(paths); return this;}");
// makeRequestData()
sw.println("@Override protected %s makeRequestData() {",
RequestData.class.getCanonicalName());
// return new RequestData("Foo::bar", new Object
sw.indentln("return new %s(\"%s\", new Object[] {%s}, propertyRefs);",
RequestData.class.getCanonicalName(), operation, parameterArray);
sw.println("}");
// handleResponse(Object obj)
sw.println("@Override protected void handleResult(Object obj) {");
sw.indent();
sw.println("Object decoded;");
if (request.isCollectionType()) {
// Lists are ArrayLists, Sets are HashSets
Class<?> collectionType = request.getCollectionType().equals(
CollectionType.LIST) ? ArrayList.class : HashSet.class;
// decoded = new ArrayList<Foo>();
sw.println("decoded = new %s();", collectionType.getCanonicalName());
// decodeReturnObjectList(FooEntityProxy.class,obj, (List)decoded);
String decodeMethod = request.isValueType() ? "decodeReturnValueList"
: "decodeReturnObjectList";
sw.println("%s(%s.class, obj, (%s)decoded);", decodeMethod,
request.getCollectionElementType().getQualifiedSourceName(),
collectionType.getCanonicalName());
} else if (request.isValueType()) {
// decoded = ValueCodex.cFString(Integer.class, String.valueOf(obj));
sw.println(
"decoded = %s.convertFromString(%s.class, String.valueOf(obj));",
ValueCodex.class.getCanonicalName(),
request.getDataType().getQualifiedSourceName());
} else if (request.isEntityType()) {
sw.println("decoded = decodeReturnObject(%s.class, obj);",
request.getEntityType().getQualifiedSourceName());
} else {
sw.println("throw new UnsupportedOperationException()");
}
// succeed((Integer) decoded);
sw.println("succeed((%s) decoded);",
request.getDataType().getParameterizedQualifiedSourceName());
sw.outdent();
sw.println("}");
sw.outdent();
sw.println("}");
// Instantiate, enqueue, and return
sw.println("X x = new X();");
// See comment in AbstractRequest.using(EntityProxy)
if (!request.isInstance()) {
sw.println("addInvocation(x);");
}
sw.println("return x;");
sw.outdent();
sw.println("}");
}
sw.commit(logger);
}
}
private void writeContextMethods(SourceWriter sw) {
for (ContextMethod method : model.getMethods()) {
// public FooService foo() {
sw.println("public %s %s() {", method.getQualifiedSourceName(),
method.getMethodName());
// return new FooServiceImpl(this);
sw.indentln("return new %s(this);", method.getQualifiedSourceName());
sw.println("}");
}
}
private void writeTypeMap(SourceWriter sw) {
sw.println("private static final %1$s<String, Class<?>> tokensToTypes"
+ " = new %1$s<String, Class<?>>();", HashMap.class.getCanonicalName());
sw.println("private static final %1$s<Class<?>, String> typesToTokens"
+ " = new %1$s<Class<?>, String>();", HashMap.class.getCanonicalName());
sw.println("static {");
sw.indent();
for (EntityProxyModel type : model.getAllProxyModels()) {
// tokensToTypes.put("Foo", Foo.class);
sw.println("tokensToTypes.put(\"%1$s\", %1$s.class);",
type.getQualifiedSourceName());
// typesToTokens.put(Foo.class, Foo);
sw.println("typesToTokens.put(%1$s.class, \"%1$s\");",
type.getQualifiedSourceName());
}
sw.outdent();
sw.println("}");
// Write instance methods
sw.println("@Override protected Class getTypeFromToken(String typeToken) {");
sw.indentln("return tokensToTypes.get(typeToken);");
sw.println("}");
sw.println("@Override protected String getTypeToken(Class type) {");
sw.indentln("return typesToTokens.get(type);");
sw.println("}");
}
}