package org.tessell.generators.views; import static joist.sourcegen.Argument.arg; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import joist.sourcegen.Access; import joist.sourcegen.Argument; import joist.sourcegen.GClass; import joist.sourcegen.GField; import joist.sourcegen.GMethod; import joist.util.Join; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.tessell.generators.GenUtils; import org.tessell.generators.css.CssGenerator; import org.tessell.generators.css.CssStubGenerator; import org.tessell.generators.resources.ResourcesGenerator; import org.tessell.gwt.dom.client.GwtElement; import org.tessell.gwt.dom.client.IsElement; import org.tessell.gwt.dom.client.IsStyle; import org.tessell.gwt.user.client.ui.IsWidget; import org.tessell.widgets.HasEnsureDebugIdSuffix; import org.tessell.widgets.StubView; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiTemplate; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.UIObject; /** A {@code ui.xml} file. */ class UiXmlFile { private final ViewGenerator viewGenerator; private final File uiXml; final File uiXmlCopy; final String baseName; final GClass gwtView; final GClass isView; final GClass stubView; // the handler is only created if we have to parser the ui.xml file private UiXmlHandler handler; UiXmlFile(ViewGenerator viewGenerator, final File uiXml) { this.viewGenerator = viewGenerator; this.uiXml = uiXml; uiXmlCopy = ResourcesGenerator.fileInOutputDirectory(viewGenerator.input, viewGenerator.output, uiXml, ".ui.xml", "-gen.ui.xml"); final String templateClassName = deriveClassName(); final String packageName = StringUtils.substringBeforeLast(templateClassName, "."); baseName = StringUtils.substringAfterLast(templateClassName, "."); isView = new GClass(packageName + ".Is" + baseName); gwtView = new GClass(packageName + ".Gwt" + baseName); stubView = new GClass(packageName + ".Stub" + baseName); } /** @return whether the {@code ui.xml} file is newer than the last {@code IsXxx} output. */ public boolean hasChanged() { File isFile = new File(viewGenerator.output, isView.getFileName()); if (!isFile.exists()) { return true; } return uiXml.lastModified() > isFile.lastModified(); } public void generate() throws Exception { System.out.println(uiXml); handler = new UiXmlHandler(); viewGenerator.parser.parse(uiXml, handler); generateIsView(); generateGwtView(); generateStubView(); } private void generateIsView() throws Exception { isView.baseClass(IsWidget.class); isView.setInterface(); isView.implementsInterface(HasEnsureDebugIdSuffix.class); // methods for each ui:with for (final UiWithDeclaration uiField : handler.withFields) { isView.getMethod(uiField.name).returnType(uiField.type); } // methods for each ui:field for (final UiFieldDeclaration uiField : handler.uiFields) { if (!uiField.isAnonymous()) { isView.getMethod(uiField.name).returnType(viewGenerator.config.getInterface(uiField.type)); } } // methods for each ui:style for (final UiStyleDeclaration style : handler.styleFields) { isView.getMethod(style.name).returnType(style.type); } // extra ensureDebugIdSuffix isView.getMethod("ensureDebugIdSuffix", arg("String", "suffix")); viewGenerator.markAndSave(isView); } private void generateGwtView() throws Exception { gwtView.baseClass(Composite.class).implementsInterface(isView.getSimpleName()); final GMethod cstr = gwtView.getConstructor(); gwtView.addImports(GWT.class); final GMethod debugId = gwtView.getMethod("onEnsureDebugId").argument("String", "baseDebugId"); debugId.addAnnotation("@Override").setProtected(); debugId.body.line("super.onEnsureDebugId(baseDebugId);"); { // uibinder final GClass uibinder = gwtView.getInnerClass("MyUiBinder").setInterface(); uibinder.baseClassName("{}<{}, {}>", UiBinder.class.getName(), handler.firstTagType, gwtView.getSimpleName()); uibinder.addAnnotation("@UiTemplate(\"{}\")", uiXml.getName().replace(".ui.xml", "-gen.ui.xml")); gwtView.addImports(UiTemplate.class); gwtView.getField("binder").type("MyUiBinder").setStatic().setFinal().initialValue("GWT.create(MyUiBinder.class)"); } { String uiXmlContent = FileUtils.readFileToString(uiXml); uiXmlContent = ResourcesGenerator.doMozWebkitSubstitution(uiXmlContent); // use the tessell subclasses that implement the IsXxx interfaces uiXmlContent = uiXmlContent.replace("urn:import:com.google.gwt.user.client.ui", "urn:import:org.tessell.gwt.user.client.ui"); GenUtils.saveIfChanged(uiXmlCopy, uiXmlContent); viewGenerator.cleanup.markOkay(uiXmlCopy); } // for each ui:with, add a @UiField(provided=true) field, getter method, and a constructor arg for (final UiWithDeclaration field : handler.withFields) { gwtView.getField(field.name).type(field.type).setAccess(Access.PACKAGE).addAnnotation("@UiField(provided = true)"); gwtView.getMethod(field.name).returnType(field.type).body.line("return {};", field.name); gwtView.addImports(UiField.class); cstr.argument(field.type, field.name); cstr.body.line("this.{} = {};", field.name, field.name); } // for each ui:style, make @UiField fields, getter methods, plus Css class for each ui:style for (final UiStyleDeclaration style : handler.styleFields) { gwtView.getField(style.name).type(style.type).setAccess(Access.PACKAGE).addAnnotation("@UiField"); gwtView.getMethod(style.name).returnType(style.type).body.line("return {};", style.name); gwtView.addImports(UiField.class); new CssGenerator(style.getCssInFile(), viewGenerator.cleanup, style.type, viewGenerator.output).run(); } // for each ui:field, make @UiField (usually provided=true) and getter methods for (final UiFieldDeclaration field : handler.uiFields) { if (field.isAnonymous()) { continue; } final String interfaceType = viewGenerator.config.getInterface(field.type); final GField f = gwtView.getField(field.name); final GMethod m = gwtView.getMethod(field.name).returnType(interfaceType); if (field.isElement) { f.type(field.type).setAccess(Access.PACKAGE).addAnnotation("@UiField"); m.body.line("return new {}({});", GwtElement.class.getName(), field.name); } else { // let UiBinder instantiate the type, which as a bonus means it will handle UiConstructor logic f.type(field.type).setAccess(Access.PACKAGE).addAnnotation("@UiField"); m.body.line("return {};", field.name); } gwtView.addImports(UiField.class); if (field.isElement) { debugId.body.line("UIObject.ensureDebugId({}, baseDebugId + \"-{}\");", field.name, field.name); gwtView.addImports(UIObject.class); } else { debugId.body.line("{}.ensureDebugId(baseDebugId + \"-{}\");", field.name, field.name); } } cstr.body.line("initWidget(binder.createAndBindUi(this).asWidget());"); String viewDebugId = gwtView.getSimpleName().replaceAll("View$", "").replaceAll("^Gwt", ""); cstr.body.line("ensureDebugId(\"{}\");", viewDebugId); gwtView.getMethod("ensureDebugIdSuffix", arg("String", "suffix")).addAnnotation("@Override").body // .line("ensureDebugId(\"{}-\" + suffix);", viewDebugId); // add implements of getStyle and getIsElement GMethod getStyle = gwtView.getMethod("getStyle").returnType(IsStyle.class).addAnnotation("@Override"); getStyle.body.line("return getIsElement().getStyle();"); GMethod getIsElement = gwtView.getMethod("getIsElement").returnType(IsElement.class).addAnnotation("@Override"); getIsElement.body.line("return new GwtElement(getElement());"); gwtView.addImports(GwtElement.class); GMethod getIsParent = gwtView.getMethod("getIsParent").returnType(IsWidget.class).addAnnotation("@Override"); getIsParent.body.line("return (IsWidget) getParent();"); viewGenerator.markAndSave(gwtView); } private void generateStubView() throws Exception { stubView.baseClass(StubView.class).implementsInterface(isView.getSimpleName()); // find any stubs (and ui:withs) that need parameters, sorted by simple name final Map<String, Argument> cstrArguments = new TreeMap<String, Argument>(); for (String cstrType : getStubDependencies()) { String simpleName = ViewGenerator.simpleName(cstrType); cstrArguments.put(simpleName, arg(cstrType, simpleName)); } // and any ui:withs, also using simple name for the arg name, so we use // the same namespace as the stub dependencies for (UiWithDeclaration with : handler.withFields) { String simpleName = ViewGenerator.simpleName(with.type); cstrArguments.put(simpleName, arg(with.type, simpleName)); } final GMethod cstr = stubView.getConstructor(new ArrayList<Argument>(cstrArguments.values())); final GMethod debugId = stubView.getMethod("onEnsureDebugId").argument("String", "baseDebugId"); debugId.addAnnotation("@Override").setProtected(); debugId.body.line("super.onEnsureDebugId(baseDebugId);"); // for each ui:field, add a field assigned to the stub type, and a getter method for (final UiFieldDeclaration field : handler.uiFields) { final String stubType = viewGenerator.config.getStub(field.type); // our stub may take cstr params final List<String> stubConstructorTypes = viewGenerator.config.getStubCstrParams(stubType); final List<String> stubConstructorNames = new ArrayList<String>(); for (String cstrType : stubConstructorTypes) { stubConstructorNames.add(ViewGenerator.simpleName(cstrType)); } stubView.getField(field.name).type(stubType).setFinal(); if (!field.isAnonymous()) { stubView.getMethod(field.name).addOverride().returnType(stubType).body.line("return {};", field.name); } debugId.body.line("{}.ensureDebugId(baseDebugId + \"-{}\");", field.name, field.name); // now use cstrNames in the call to new cstr.body.line("this.{} = new {}({});", field.name, stubType, Join.commaSpace(stubConstructorNames)); } // for each ui:with, add a field, field assignment, and getter for (UiWithDeclaration with : handler.withFields) { stubView.getField(with.name).type(with.type).setFinal(); stubView.getMethod(with.name).addAnnotation("@Override").returnType(with.type).body.line("return {};", with.name); cstr.body.line("this.{} = {};", with.name, ViewGenerator.simpleName(with.type)); } List<UiFieldDeclaration> uiFieldWidgets = handler.uiFieldWidgets(); if (uiFieldWidgets.size() > 0) { UiFieldDeclaration first = uiFieldWidgets.get(0); cstr.body.line("setWidget({});", first.name); } for (final UiFieldDeclaration field : uiFieldWidgets) { if (field.parentOperation != null) { cstr.body.line("{}({});", field.parentOperation, field.name); } } // for each ui:style, make @UiField fields, getter method, plus StubCss class for (final UiStyleDeclaration style : handler.styleFields) { CssStubGenerator g = new CssStubGenerator(style.getCssInFile(), viewGenerator.cleanup, style.type, viewGenerator.output); g.run(); stubView.getField(style.name).type(style.type).setFinal().initialValue("new {}()", g.getCssStubClassName()); stubView.getMethod(style.name).returnType(style.type).body.line("return {};", style.name); } final String viewDebugId = stubView.getSimpleName().replaceAll("View$", "").replaceAll("^Stub", ""); cstr.body.line("ensureDebugId(\"{}\");", viewDebugId); stubView.getMethod("ensureDebugIdSuffix", arg("String", "suffix")).addAnnotation("@Override").body // .line("ensureDebugId(\"{}-\" + suffix);", viewDebugId); viewGenerator.markAndSave(stubView); } String getPath() { return uiXml.getPath(); } /** @return the ui:with fields, if we parsed the file. */ List<UiWithDeclaration> getFreshWiths() { return handler.withFields; } List<UiWithDeclaration> getPossiblyCachedWiths(UiXmlCache cache) { return handler == null ? cache.getCachedWiths(this) : getFreshWiths(); } /** @return the ui:style fields, if we parsed the file. */ List<UiStyleDeclaration> getFreshStyles() { return handler.styleFields; } List<UiStyleDeclaration> getPossiblyCachedStyles(UiXmlCache cache) { return handler == null ? cache.getCachedStyles(this) : getFreshStyles(); } List<String> getStubDependencies() { final Set<String> stubDependencies = new TreeSet<String>(); for (final UiFieldDeclaration field : handler.uiFields) { final String stubType = viewGenerator.config.getStub(field.type); stubDependencies.addAll(viewGenerator.config.getStubCstrParams(stubType)); } return new ArrayList<String>(stubDependencies); } List<String> getPossiblyCachedStubDependencies(UiXmlCache cache) { return handler == null ? cache.getCachedStubDependencies(this) : getStubDependencies(); } private String deriveClassName() { // Get the absolute path, drop off the input path, drop ui.xml, / -> . return appendViewIfNeeded(uiXml .getAbsolutePath() .replace(viewGenerator.input.getPath() + File.separator, "") .replace(".ui.xml", "") .replace(File.separatorChar, '.')); } private String appendViewIfNeeded(String input) { return input.endsWith("View") ? input : input + "View"; } }