/* * Copyright 2011 cruxframework.org. * * 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 org.cruxframework.crux.core.rebind.controller; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.cruxframework.crux.core.client.Crux; import org.cruxframework.crux.core.client.collection.FastMap; import org.cruxframework.crux.core.client.controller.Controller; import org.cruxframework.crux.core.client.controller.Expose; import org.cruxframework.crux.core.client.controller.Factory; import org.cruxframework.crux.core.client.controller.Validate; import org.cruxframework.crux.core.client.event.BaseEvent; import org.cruxframework.crux.core.client.formatter.HasFormatter; import org.cruxframework.crux.core.client.screen.views.View; import org.cruxframework.crux.core.client.screen.views.ViewAware; import org.cruxframework.crux.core.rebind.AbstractProxyCreator; import org.cruxframework.crux.core.rebind.CruxGeneratorException; import org.cruxframework.crux.core.rebind.context.RebindContext; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JPackage; import com.google.gwt.core.ext.typeinfo.JParameter; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.NotFoundException; import com.google.gwt.dev.generator.NameFactory; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.logging.client.LogConfiguration; import com.google.gwt.user.client.ui.HasText; import com.google.gwt.user.client.ui.HasValue; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; /** * @author Thiago da Rosa de Bustamante * */ public class ControllerProxyCreator extends AbstractProxyCreator { public static final String CONTROLLER_PROXY_SUFFIX = "_ControllerProxy"; public static final String EXPOSED_METHOD_SUFFIX = "_Exposed_"; private final JClassType controllerClass; private String controllerName; /** * Constructor * * @param logger * @param context * @param crossDocumentIntf */ public ControllerProxyCreator(RebindContext context, JClassType controllerClass) { super(context, false); this.controllerClass = controllerClass; Controller controllerAnnot = controllerClass.getAnnotation(Controller.class); this.controllerName = controllerAnnot.value(); } /** * @see org.cruxframework.crux.core.rebind.AbstractProxyCreator#generateProxyContructor(com.google.gwt.user.rebind.SourcePrinter) */ @Override protected void generateProxyContructor(SourcePrinter srcWriter) { srcWriter.println(); srcWriter.println("public " + getProxySimpleName() + "("+View.class.getCanonicalName()+" view) {"); srcWriter.println("this.__view = view;"); srcWriter.println("}"); } /** * @see org.cruxframework.crux.core.rebind.AbstractProxyCreator#generateProxyFields(com.google.gwt.user.rebind.SourcePrinter) */ @Override protected void generateProxyFields(SourcePrinter srcWriter) throws CruxGeneratorException { super.generateProxyFields(srcWriter); srcWriter.println("private " + View.class.getCanonicalName() + " __view;"); generateLoggerField(srcWriter); srcWriter.println(); } @Override protected void generateProxyMethods(SourcePrinter srcWriter) { super.generateProxyMethods(srcWriter); generateGetViewMethod(srcWriter); generateControllerOverideExposedMethods(srcWriter); } /** * * @param srcWriter */ protected void generateGetViewMethod(SourcePrinter srcWriter) { srcWriter.println("public String getBoundCruxViewId(){"); srcWriter.println("return (this.__view==null?null:this.__view.getId());"); srcWriter.println("}"); srcWriter.println("public "+View.class.getCanonicalName()+" getBoundCruxView(){"); srcWriter.println("return this.__view;"); srcWriter.println("}"); } /** * @return */ protected String[] getImports() { String[] imports = new String[] { GWT.class.getCanonicalName(), org.cruxframework.crux.core.client.screen.Screen.class.getCanonicalName(), FastMap.class.getCanonicalName(), BaseEvent.class.getCanonicalName(), GwtEvent.class.getCanonicalName(), HasValue.class.getCanonicalName(), HasText.class.getCanonicalName(), HasFormatter.class.getCanonicalName(), Widget.class.getCanonicalName(), RunAsyncCallback.class.getCanonicalName(), Crux.class.getCanonicalName(), Logger.class.getCanonicalName(), LogConfiguration.class.getCanonicalName(), Level.class.getCanonicalName() }; return imports; } /** * @return the full qualified name of the proxy object. */ public String getProxyQualifiedName() { return controllerClass.getPackage().getName() + "." + getProxySimpleName(); } /** * @return the simple name of the proxy object. */ public String getProxySimpleName() { return controllerClass.getSimpleSourceName() + CONTROLLER_PROXY_SUFFIX; } /** * @return a sourceWriter for the proxy class */ protected SourcePrinter getSourcePrinter() { JPackage pkg = controllerClass.getPackage(); String packageName = pkg == null ? "" : pkg.getName(); PrintWriter printWriter = context.getGeneratorContext().tryCreate(context.getLogger(), packageName, getProxySimpleName()); if (printWriter == null) { return null; } ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, getProxySimpleName()); String[] imports = getImports(); for (String imp : imports) { composerFactory.addImport(imp); } composerFactory.setSuperclass(controllerClass.getQualifiedSourceName()); composerFactory.addImplementedInterface(ViewAware.class.getCanonicalName()); return new SourceCodePrinter(composerFactory.createSourceWriter(context.getGeneratorContext(), printWriter), context.getLogger()); } /** * Generates the signature for the exposed method * * @param w * @param nameFactory * @param method */ protected void generateProxyExposedMethodSignature(SourcePrinter w, NameFactory nameFactory, JMethod method) { // Write the method signature JType returnType = method.getReturnType().getErasedType(); w.print("public "); w.print(returnType.getQualifiedSourceName()); w.print(" "); w.print(method.getName()+EXPOSED_METHOD_SUFFIX + "("); generateMethodParameters(w, nameFactory, method); w.print(")"); generateMethodTrhowsClause(w, method); w.println(); } /** * @param sourceWriter */ private void generateControllerOverideExposedMethods(SourcePrinter sourceWriter) { List<JMethod> methods = new ArrayList<JMethod>(); JMethod[] controllerMethods = controllerClass.getOverridableMethods(); for (JMethod jMethod : controllerMethods) { if (isControllerMethodSignatureValid(jMethod)) { methods.add(jMethod); } } Set<String> processed = new HashSet<String>(); for (JMethod method: methods) { String methodSignature = method.getReadableDeclaration(true, true, true, true, true); if (!processed.contains(methodSignature)) { processed.add(methodSignature); generateProxyExposedMethodSignature(sourceWriter, new NameFactory(), method); sourceWriter.println("{"); logDebugMessage(sourceWriter, "\"Calling client method: Controller["+controllerName+"], Method["+method.getName()+"]\""); JType returnType = method.getReturnType().getErasedType(); boolean hasReturn = returnType != JPrimitiveType.VOID; Validate annot = method.getAnnotation(Validate.class); boolean mustValidade = annot != null; if (mustValidade) { sourceWriter.println("try{"); String validateMethod = annot.value(); if (validateMethod == null || validateMethod.length() == 0) { String methodName = method.getName(); methodName = Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1); validateMethod = "validate"+ methodName; } generateMethodvalidationCall(sourceWriter, method, validateMethod); sourceWriter.println("}catch (Throwable e1){"); sourceWriter.println("Crux.getValidationErrorHandler().handleValidationError(e1.getLocalizedMessage());"); logDebugMessage(sourceWriter, "\"Client method not called due to a Validation error: Controller["+controllerName+"], Method["+method.getName()+"]\""); if (hasReturn) { sourceWriter.println("return null;"); } else { sourceWriter.println("return;"); } sourceWriter.println("}"); } if (hasReturn) { sourceWriter.print(returnType.getQualifiedSourceName()+" ret = "); } generateExposedMethodCall(sourceWriter, method); logDebugMessage(sourceWriter, "\"Client method executed: Controller["+controllerName+"], Method["+method.getName()+"]\""); if (hasReturn) { sourceWriter.println("return ret;"); } sourceWriter.println("}"); } } } /** * @param sourceWriter * @param method */ private void generateExposedMethodCall(SourcePrinter sourceWriter, JMethod method) { sourceWriter.print(method.getName()+"("); boolean needsComma = false; JParameter[] params = method.getParameters(); for (int i = 0; i < params.length; ++i) { JParameter param = params[i]; if (needsComma) { sourceWriter.print(", "); } else { needsComma = true; } String paramName = param.getName(); sourceWriter.print(paramName); } sourceWriter.println(");"); } /** * @param sourceWriter * @param method */ private void generateMethodvalidationCall(SourcePrinter sourceWriter, JMethod method, String validationMethod) { sourceWriter.print(validationMethod+"("); JParameter[] params = method.getParameters(); if (params.length == 1) { JParameter param = params[0]; JMethod validate = controllerClass.findMethod(validationMethod, new JType[]{param.getType()}); if (validate != null) { sourceWriter.print(param.getName()); } } sourceWriter.println(");"); } /** * Verify if a method must be included in the list of callable methods in the * generated invoker class * @param method * @return */ private boolean isControllerMethodSignatureValid(JMethod method) { try { if (!method.isPublic()) { return false; } boolean exposed = method.getAnnotation(Expose.class) != null; boolean factory = method.getAnnotation(Factory.class) != null; if ((exposed && factory) || (!exposed && !factory)) { return false; } JParameter[] parameters = method.getParameters(); if (exposed) { if (parameters != null && parameters.length != 0 && parameters.length != 1) { return false; } if (parameters != null && parameters.length == 1) { JClassType gwtEventType = controllerClass.getOracle().getType(GwtEvent.class.getCanonicalName()); JClassType nativeEventType = controllerClass.getOracle().getType(NativeEvent.class.getCanonicalName()); JClassType cruxEventType = controllerClass.getOracle().getType(BaseEvent.class.getCanonicalName()); JClassType parameterType = parameters[0].getType().isClassOrInterface(); if (parameterType == null || (!gwtEventType.isAssignableFrom(parameterType) && !cruxEventType.isAssignableFrom(parameterType) && !nativeEventType.isAssignableFrom(parameterType))) { return false; } } } else if (factory) { if (parameters == null || parameters.length != 1) { return false; } JType returnType = method.getReturnType().getErasedType(); boolean hasReturn = returnType != JPrimitiveType.VOID; if (!hasReturn) { return false; } } JClassType objectType = controllerClass.getOracle().getType(Object.class.getCanonicalName()); if (method.getEnclosingType().equals(objectType)) { return false; } return true; } catch (NotFoundException e) { throw new CruxGeneratorException(e.getMessage(), e); } } }