| /* |
| * 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().getParameterizedQualifiedSourceName(); |
| 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("}"); |
| } |
| } |