/* * 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(); /* * The paramaterization 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()); } } String delegateSimpleName = String.format( "%s_%s", escapedBinaryName(maybeParameterizedName.toString()), 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('.', '_'); } /** * Create an EditorContext implementation that will provide acess 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 = escapedBinaryName(parent.getEditorType().getName()) + "_" + 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("}"); } }