blob: 4039f3cf8da5a608c944523b316cdd7389684f3c [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.editor.rebind;
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.TypeOracle;
import com.google.gwt.dev.generator.NameFactory;
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.Name.BinaryName;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorVisitor;
import com.google.gwt.editor.client.impl.AbstractEditorContext;
import com.google.gwt.editor.client.impl.RootEditorContext;
import com.google.gwt.editor.rebind.model.EditorData;
import com.google.gwt.editor.rebind.model.EditorModel;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* A base class for generating Editor drivers.
*/
public abstract class AbstractEditorDriverGenerator extends Generator {
private GeneratorContext context;
private TreeLogger logger;
private EditorModel 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 EditorModel(logger, toGenerate,
oracle.findType(getDriverInterfaceType().getName()));
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
packageName, simpleSourceName);
factory.setSuperclass(Name.getSourceNameForClass(getDriverSuperclassType())
+ "<" + model.getProxyType().getParameterizedQualifiedSourceName()
+ ", " + model.getEditorType().getParameterizedQualifiedSourceName()
+ ">");
factory.addImplementedInterface(typeName);
SourceWriter sw = factory.createSourceWriter(context, pw);
writeCreateDelegate(sw);
writeAdditionalContent(logger, context, model, sw);
sw.commit(logger);
return factory.getCreatedClassName();
}
protected abstract Class<?> getDriverInterfaceType();
protected abstract Class<?> getDriverSuperclassType();
protected String getEditorDelegate(EditorData delegateData) {
JClassType edited = delegateData.getEditedType();
JClassType editor = delegateData.getEditorType();
Map<EditorData, String> delegateFields = new IdentityHashMap<EditorData, String>();
NameFactory nameFactory = new NameFactory();
String delegateSimpleName = String.format(
"%s_%s",
escapedMaybeParameterizedBinaryName(editor),
BinaryName.getShortClassName(Name.getBinaryNameForClass(getEditorDelegateType())));
String packageName = editor.getPackage().getName();
PrintWriter pw = context.tryCreate(logger, packageName, delegateSimpleName);
if (pw != null) {
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
packageName, delegateSimpleName);
factory.setSuperclass(String.format("%s",
Name.getSourceNameForClass(getEditorDelegateType())));
SourceWriter sw = factory.createSourceWriter(context, pw);
EditorData[] data = model.getEditorData(editor);
/*
* Declare fields in the generated subclass for the editor and the object
* being edited. This decreases casting over having a generic field in the
* supertype.
*/
sw.println("private %s editor;", editor.getQualifiedSourceName());
sw.println("@Override protected %s getEditor() {return editor;}",
editor.getQualifiedSourceName());
sw.println(
"protected void setEditor(%s editor) {this.editor=(%s)editor;}",
Editor.class.getCanonicalName(), editor.getQualifiedSourceName());
sw.println("private %s object;", edited.getQualifiedSourceName());
sw.println("@Override public %s getObject() {return object;}",
edited.getQualifiedSourceName());
sw.println(
"@Override protected void setObject(Object object) {this.object=(%s)object;}",
edited.getQualifiedSourceName());
if (delegateData.isCompositeEditor()) {
sw.println("@Override protected %s createComposedDelegate() {",
Name.getSourceNameForClass(this.getEditorDelegateType()));
sw.indentln("return new %s();",
getEditorDelegate(delegateData.getComposedData()));
sw.println("}");
}
// Fields for the sub-delegates that must be managed
for (EditorData d : data) {
if (d.isDelegateRequired()) {
String fieldName = nameFactory.createName(d.getPropertyName()
+ "Delegate");
delegateFields.put(d, fieldName);
sw.println("%s %s;",
Name.getSourceNameForClass(getEditorDelegateType()), fieldName);
}
}
// For each entity property, create a sub-delegate and initialize
sw.println("@Override protected void initializeSubDelegates() {");
sw.indent();
if (delegateData.isCompositeEditor()) {
sw.println(
"createChain(%s.class);",
delegateData.getComposedData().getEditedType().getQualifiedSourceName());
}
for (EditorData d : data) {
String subDelegateType = getEditorDelegate(d);
if (d.isDelegateRequired()) {
sw.println("if (editor.%s != null) {", d.getSimpleExpression());
sw.indent();
sw.println("%s = new %s();", delegateFields.get(d), subDelegateType);
sw.println("addSubDelegate(%s, appendPath(\"%s\"), editor.%s);",
delegateFields.get(d), d.getDeclaredPath(),
d.getSimpleExpression());
sw.outdent();
sw.println("}");
}
}
sw.outdent();
sw.println("}");
sw.println("@Override public void accept(%s visitor) {",
EditorVisitor.class.getCanonicalName());
sw.indent();
if (delegateData.isCompositeEditor()) {
sw.println("getEditorChain().accept(visitor);");
}
for (EditorData d : data) {
if (d.isDelegateRequired()) {
sw.println("if (%s != null) ", delegateFields.get(d));
}
sw.println("{");
sw.indent();
String editorContextName = getEditorContext(delegateData, d);
sw.println(
"%s ctx = new %s(getObject(), editor.%s, appendPath(\"%s\"));",
editorContextName, editorContextName, d.getSimpleExpression(),
d.getDeclaredPath());
if (d.isDelegateRequired()) {
sw.println("ctx.setEditorDelegate(%s);", delegateFields.get(d));
}
sw.println("ctx.traverse(visitor, %s);", d.isDelegateRequired()
? delegateFields.get(d) : "null");
sw.outdent();
sw.println("}");
}
sw.outdent();
sw.println("}");
sw.commit(logger);
}
return packageName + "." + delegateSimpleName;
}
protected abstract Class<?> getEditorDelegateType();
protected abstract String mutableObjectExpression(EditorData data,
String sourceObjectExpression);
protected void writeAdditionalContent(TreeLogger logger,
GeneratorContext context, EditorModel model, SourceWriter sw)
throws UnableToCompleteException {
}
private String escapedBinaryName(String binaryName) {
return binaryName.replace("_", "_1").replace('$', '_').replace('.', '_');
}
private String escapedMaybeParameterizedBinaryName(JClassType editor) {
/*
* The parameterization of the editor type is included to ensure that a
* correct specialization of a CompositeEditor will be generated. For
* example, a ListEditor<Person, APersonEditor> would need a different
* delegate from a ListEditor<Person, AnotherPersonEditor>.
*/
StringBuilder maybeParameterizedName = new StringBuilder(
BinaryName.getClassName(editor.getQualifiedBinaryName()));
if (editor.isParameterized() != null) {
for (JClassType type : editor.isParameterized().getTypeArgs()) {
maybeParameterizedName.append("$").append(type.getQualifiedBinaryName());
}
}
return escapedBinaryName(maybeParameterizedName.toString());
}
/**
* Create an EditorContext implementation that will provide access to
* {@link data} owned by {@link parent}. In other words, given the EditorData
* for a {@code PersonEditor} and the EditorData for a {@code AddressEditor}
* nested in the {@code PersonEditor}, create an EditorContext that will
* describe the relationship.
*
* @return the qualified name of the EditorContext implementation
*/
private String getEditorContext(EditorData parent, EditorData data) {
String pkg = parent.getEditorType().getPackage().getName();
// PersonEditor_manager_name_Context
String simpleName =
escapedMaybeParameterizedBinaryName(parent.getEditorType())
+ "_" + data.getDeclaredPath().replace("_", "_1").replace(".", "_")
+ "_Context";
PrintWriter pw = context.tryCreate(logger, pkg, simpleName);
if (pw != null) {
ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
pkg, simpleName);
String editedSourceName = data.getEditedType().getParameterizedQualifiedSourceName();
f.setSuperclass(AbstractEditorContext.class.getCanonicalName() + "<"
+ editedSourceName + ">");
SourceWriter sw = f.createSourceWriter(context, pw);
String parentSourceName = parent.getEditedType().getQualifiedSourceName();
sw.println("private final %s parent;", parentSourceName);
sw.println("public %s(%s parent, %s<%s> editor, String path) {",
simpleName, parentSourceName, Editor.class.getCanonicalName(),
editedSourceName);
sw.indentln("super(editor,path);");
sw.indentln("this.parent = parent;");
sw.println("}");
sw.println("@Override public boolean canSetInModel() {");
sw.indentln("return parent != null && %s && %s;",
data.getSetterName() == null ? "false" : "true",
data.getBeanOwnerGuard("parent"));
sw.println("}");
sw.println("@Override public %s checkAssignment(Object value) {",
editedSourceName);
sw.indentln("return (%s) value;", editedSourceName);
sw.println("}");
sw.println(
"@Override public Class getEditedType() { return %s.class; }",
data.getEditedType().getQualifiedSourceName());
sw.println("@Override public %s getFromModel() {", editedSourceName);
sw.indentln("return (parent != null && %s) ? parent%s%s : null;",
data.getBeanOwnerGuard("parent"), data.getBeanOwnerExpression(),
data.getGetterExpression());
sw.println("}");
sw.println("@Override public void setInModel(%s data) {",
editedSourceName);
if (data.getSetterName() == null) {
sw.indentln("throw new UnsupportedOperationException();");
} else {
sw.indentln("parent%s.%s(data);", data.getBeanOwnerExpression(),
data.getSetterName());
}
sw.println("}");
sw.commit(logger);
}
return pkg + "." + simpleName;
}
private void writeCreateDelegate(SourceWriter sw)
throws UnableToCompleteException {
String editorDelegateName = getEditorDelegate(model.getRootData());
sw.println("@Override public void accept(%s visitor) {",
EditorVisitor.class.getCanonicalName());
sw.indent();
sw.println("%1$s ctx = new %1$s(getDelegate(), %2$s.class, getObject());",
RootEditorContext.class.getCanonicalName(),
model.getProxyType().getQualifiedSourceName());
sw.println("ctx.traverse(visitor, getDelegate());");
sw.outdent();
sw.println("}");
sw.println("@Override protected %s createDelegate() {",
Name.getSourceNameForClass(getEditorDelegateType()),
model.getProxyType().getQualifiedSourceName(),
model.getEditorType().getQualifiedSourceName());
sw.indent();
sw.println("return new %1$s();", editorDelegateName);
sw.outdent();
sw.println("}");
}
}