SqlProxyCreator.java :  » GWT » gwt-mobile-webkit » com » google » code » gwt » database » rebind » Java Open Source

Java Open Source » GWT » gwt mobile webkit 
gwt mobile webkit » com » google » code » gwt » database » rebind » SqlProxyCreator.java
/*
 * Copyright 2009 Bart Guijt and others.
 * 
 * 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.code.gwt.database.rebind;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;

import com.google.code.gwt.database.client.Database;
import com.google.code.gwt.database.client.DatabaseException;
import com.google.code.gwt.database.client.SQLTransaction;
import com.google.code.gwt.database.client.service.Callback;
import com.google.code.gwt.database.client.service.Connection;
import com.google.code.gwt.database.client.service.DataService;
import com.google.code.gwt.database.client.service.ListCallback;
import com.google.code.gwt.database.client.service.RowIdListCallback;
import com.google.code.gwt.database.client.service.ScalarCallback;
import com.google.code.gwt.database.client.service.Select;
import com.google.code.gwt.database.client.service.Update;
import com.google.code.gwt.database.client.service.VoidCallback;
import com.google.code.gwt.database.client.service.callback.DataServiceStatementCallback;
import com.google.code.gwt.database.client.service.callback.list.StatementCallbackListCallback;
import com.google.code.gwt.database.client.service.callback.list.TransactionCallbackListCallback;
import com.google.code.gwt.database.client.service.callback.rowid.StatementCallbackRowIdListCallback;
import com.google.code.gwt.database.client.service.callback.rowid.TransactionCallbackRowIdListCallback;
import com.google.code.gwt.database.client.service.callback.scalar.TransactionCallbackScalarCallback;
import com.google.code.gwt.database.client.service.callback.voyd.StatementCallbackVoidCallback;
import com.google.code.gwt.database.client.service.callback.voyd.TransactionCallbackVoidCallback;
import com.google.code.gwt.database.client.service.impl.BaseDataService;
import com.google.code.gwt.database.client.service.impl.DataServiceUtils;
import com.google.code.gwt.database.client.util.StringUtils;
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.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.dev.util.Util;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * Helper class for the {@link DataServiceGenerator}.
 * 
 * <p>
 * This class is specifically instantiated for a single DataService proxy.
 * </p>
 * 
 * @author bguijt
 */
public class SqlProxyCreator {

  private static final String PROXY_SUFFIX = "_SqlProxy";

  private static final String[] IMPORTED_CLASSES = new String[] {
      Database.class.getCanonicalName(),
      SQLTransaction.class.getCanonicalName(), BaseDataService.class.getName(),
      DataServiceUtils.class.getCanonicalName(),
      VoidCallback.class.getCanonicalName(),
      ListCallback.class.getCanonicalName(),
      ScalarCallback.class.getCanonicalName(),
      RowIdListCallback.class.getCanonicalName(),
      DataServiceStatementCallback.class.getCanonicalName(),
      StatementCallbackVoidCallback.class.getCanonicalName(),
      StatementCallbackListCallback.class.getCanonicalName(),
      StatementCallbackRowIdListCallback.class.getCanonicalName(),
      TransactionCallbackVoidCallback.class.getCanonicalName(),
      TransactionCallbackScalarCallback.class.getCanonicalName(),
      TransactionCallbackListCallback.class.getCanonicalName(),
      TransactionCallbackRowIdListCallback.class.getCanonicalName(),
      DatabaseException.class.getCanonicalName()};

  private TreeLogger logger;
  private GeneratorContext context;
  private GeneratorUtils genUtils;
  private JClassType dataService;
  private SourceWriter sw;

  /**
   * <code>true</code> if the dataService directly extends DataService,
   * <code>false</code> otherwise.
   */
  private boolean isBaseType;

  public SqlProxyCreator(TreeLogger logger, GeneratorContext context,
      JClassType dataService) {
    this.logger = logger;
    this.context = context;
    this.dataService = dataService;
    this.genUtils = new GeneratorUtils(logger, context, IMPORTED_CLASSES);
  }

  public String create() throws UnableToCompleteException {
    sw = getSourceWriter();
    if (sw == null) {
      // No need to generate, it's already done. Return name of generated class.
      return getProxyQualifiedName();
    }

    generateProxyConstructor();
    if (isBaseType) {
      generateProxyOpenDatabaseMethod();
      generateProxyGetDatabaseDetailsMethod();
    }

    // Generate service methods for each defined interface method:
    for (JMethod method : dataService.getMethods()) {
      generateProxyServiceMethod(method);
    }

    sw.commit(logger);

    return getProxyQualifiedName();
  }

  /**
   * Generates the constructor.
   */
  private void generateProxyConstructor() {
    sw.println("public " + getProxySimpleName() + "() {");
    sw.indent();
    sw.println("// default empty constructor");
    sw.outdent();
    sw.println("}");
  }

  /**
   * Generates the {@link BaseDataService#openDatabase()} method
   */
  private void generateProxyOpenDatabaseMethod() {
    Connection con = dataService.getAnnotation(Connection.class);
    sw.beginJavaDocComment();
    sw.print("Opens the '" + con.name() + "' Database version " + con.version());
    sw.endJavaDocComment();
    sw.println("public final " + genUtils.getClassName(Database.class)
        + " openDatabase() throws "
        + genUtils.getClassName(DatabaseException.class) + " {");
    sw.indentln("return " + genUtils.getClassName(Database.class)
        + ".openDatabase(\"" + Generator.escape(con.name()) + "\", \""
        + Generator.escape(con.version()) + "\", \""
        + Generator.escape(con.description()) + "\", " + con.maxsize() + ");");
    sw.println("}");
  }

  /**
   * Generates the {@link BaseDataService#getDatabaseDetails()} method.
   */
  private void generateProxyGetDatabaseDetailsMethod() {
    Connection con = dataService.getAnnotation(Connection.class);
    String toReturn = "'" + Generator.escape(con.name()) + "' version "
        + Generator.escape(con.version());
    sw.beginJavaDocComment();
    sw.print("Returns the <code>" + toReturn + "</code> string.");
    sw.endJavaDocComment();
    sw.println("public final String getDatabaseDetails() {");
    sw.indentln("return \"" + toReturn + "\";");
    sw.println("}");
  }

  /**
   * Generates the proxy method implementing the specified service.
   */
  private void generateProxyServiceMethod(JMethod service)
      throws UnableToCompleteException {
    Select select = service.getAnnotation(Select.class);
    Update update = service.getAnnotation(Update.class);

    // Assertions:
    if (select == null && update == null) {
      logger.log(TreeLogger.ERROR, service.getName()
          + " has no @Select nor @Update annotation");
      throw new UnableToCompleteException();
    }
    if ((select == null || StringUtils.isEmpty(getSql(select)))
        && (update == null || StringUtils.isEmpty(getSql(update)))) {
      logger.log(TreeLogger.ERROR, service.getName()
          + ": @Select or @Update annotation has no SQL statement");
      throw new UnableToCompleteException();
    }
    JParameter[] params = service.getParameters();
    if (params.length == 0) {
      logger.log(TreeLogger.ERROR, "Method " + service.getName()
          + " must have at least one (callback) parameter");
      throw new UnableToCompleteException();
    }
    JParameter callback = params[params.length - 1];
    if (!genUtils.isAssignableToType(callback.getType(), Callback.class)) {
      logger.log(TreeLogger.ERROR, "The last parameter of method "
          + service.getName() + " is no valid Callback! Must be subtype of "
          + genUtils.getClassName(Callback.class));
      throw new UnableToCompleteException();
    }

    generateProxyServiceMethodJavadoc(service);

    sw.print("public final void " + service.getName() + "(");
    for (int i = 0; i < params.length; i++) {
      if (i > 0) {
        sw.print(", ");
      }
      sw.print("final " + genUtils.getClassName(params[i].getType()) + " "
          + params[i].getName());
    }
    sw.println(") {");
    sw.indent();

    // Depending on the callback type, create a service method body:
    ServiceMethodCreator creator = update != null ? createExecuteSqlCreator(
        service, getSql(update), update.foreach(), update)
        : createExecuteSqlCreator(service, getSql(select), null, select);

    creator.generateServiceMethodBody();

    // ends service method
    sw.outdent();
    sw.println("}");
  }

  /**
   * Generates the Javadoc for the specified service method. The usefulness of
   * this code is arguable low :-)
   */
  private void generateProxyServiceMethodJavadoc(JMethod service)
      throws UnableToCompleteException {
    Select select = service.getAnnotation(Select.class);
    Update update = service.getAnnotation(Update.class);
    sw.beginJavaDocComment();
    String stmt = null;
    sw.println("Executes the following SQL "
        + (update != null ? "Update" : "Select") + " statement:");
    stmt = update != null ? getSql(update) : getSql(select);
    sw.print("<pre>" + Util.escapeXml(stmt) + "</pre>");
    sw.endJavaDocComment();
  }

  private String getSql(Select select) {
    return (StringUtils.isEmpty(select.value())) ? select.sql()
        : select.value();
  }

  private String getSql(Update update) {
    return (StringUtils.isEmpty(update.value())) ? update.sql()
        : update.value();
  }

  private ServiceMethodCreator createExecuteSqlCreator(JMethod service,
      String sql, String foreach, Annotation query)
      throws UnableToCompleteException {
    JParameter callback = service.getParameters()[service.getParameters().length - 1];
    String serviceCreatorName = ServiceMethodCreator.class.getCanonicalName()
        + callback.getType().getSimpleSourceName();
    TreeLogger branchedLogger = logger.branch(TreeLogger.DEBUG,
        "Generating service method '" + service.getName()
            + "' with SQL statement expression '" + sql + "'");
    try {
      Class<?> creatorClass = Class.forName(serviceCreatorName);
      ServiceMethodCreator creator = (ServiceMethodCreator) creatorClass.newInstance();
      creator.setContext(context, branchedLogger, sw, service, sql, foreach,
          query, genUtils.branchWithLogger(branchedLogger));
      return creator;
    } catch (ClassNotFoundException e) {
      branchedLogger.log(TreeLogger.ERROR, "Cannot find " + serviceCreatorName
          + " class specific for callback type '"
          + callback.getType().getSimpleSourceName() + "'");
    } catch (InstantiationException e) {
      branchedLogger.log(TreeLogger.ERROR, "Cannot instantiate "
          + serviceCreatorName + " class specific for callback type '"
          + callback.getType().getSimpleSourceName() + "'");
    } catch (IllegalAccessException e) {
      branchedLogger.log(TreeLogger.ERROR, "Cannot invoke constructor of "
          + serviceCreatorName + " class specific for callback type '"
          + callback.getType().getSimpleSourceName() + "'");
    }
    throw new UnableToCompleteException();
  }

  /**
   * Returns a SourceWriter which is prepared to write the class' body.
   * 
   * @throws UnableToCompleteException
   */
  private SourceWriter getSourceWriter() throws UnableToCompleteException {
    JPackage serviceIntfPkg = dataService.getPackage();
    String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
    PrintWriter printWriter = context.tryCreate(logger, packageName,
        getProxySimpleName());

    if (printWriter == null) {
      // Proxy already exists.
      return null;
    }

    ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
        packageName, getProxySimpleName());

    for (String imp : IMPORTED_CLASSES) {
      composerFactory.addImport(imp);
    }

    JClassType superType = dataService.getImplementedInterfaces()[0];
    if (GeneratorUtils.isType(superType, DataService.class)) {
      composerFactory.setSuperclass(genUtils.getClassName(BaseDataService.class));
      isBaseType = true;
    } else {
      isBaseType = false;
      // the dataService inherits from a different interface.
      // Create another SqlProxyCreator to take care of that interface:
      SqlProxyCreator superClassCreator = new SqlProxyCreator(logger.branch(
          TreeLogger.DEBUG, "Generating proxy methods for superclass '"
              + superType.getQualifiedSourceName() + "'..."), context,
          superType);
      String className = superClassCreator.create();
      composerFactory.setSuperclass(genUtils.shortenName(className));
    }
    composerFactory.addImplementedInterface(genUtils.getClassName(dataService));

    composerFactory.setJavaDocCommentForClass("Generated by {@link "
        + genUtils.getClassName(getClass()) + "}");

    return composerFactory.createSourceWriter(context, printWriter);
  }

  /**
   * Returns the fully qualified name of the generated class.
   */
  private String getProxyQualifiedName() {
    return (dataService.getPackage() == null ? ""
        : dataService.getPackage().getName() + ".")
        + getProxySimpleName();
  }

  /**
   * Returns the name of the generated class.
   */
  private String getProxySimpleName() {
    return dataService.getName().replace('.', '_') + PROXY_SUFFIX;
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.