package com.sptci.echo2;

import java.io.FileNotFoundException;
import java.io.IOException;

import java.util.Date;
import java.util.Formatter;
import java.util.List;
import java.util.Map;

import java.text.SimpleDateFormat;

import org.jdom.JDOMException;

import com.sptci.util.StringUtilities;

/**
 * A JDO JavaBean generator.  In addition to generating the source file
 * as implemented in {@link ModelGenerator}, generates an additional
 * <code>Factory</code> class to implement interactions with the JDO
 * datastore, and a default JDO metadata file.
 *
 * <p>The following shows the invocation semantics for this class:</p>
 * <pre>
 *  java -classpath &lt;path to classes&gt;:sptecho.jar:&lt;path to Echo2_App.jar&gt;:&lt;path to jdom&gt;:&lt;path to jaxen&gt; \
 *    com.sptci.echo2.JDOModelGenerator \
 *    &lt;fully qualified name of UI class&gt;
 * </pre>
 *
 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
 * @author Rakesh Vidyadharan 2006-02-08
 * @version $Id: JDOModelGenerator.java,v 1.3 2006/02/15 17:23:32 rakesh Exp $
 * @see JDOMetaDataGenerator
 */
public class JDOModelGenerator extends ModelGenerator
{
  /**
   * Delegates to the super-class constructor.
   *
   * @param name The fully qualified name of the class.  Example:
   *   <code>com.sptci.demo.InputForm</code>.
   * @throws ClassNotFoundException If the class corresponding to the
   *   <code>name</code> cannot be found in the classpath.
   */
  public JDOModelGenerator( String name ) throws ClassNotFoundException
  {
    super( name );
  }

  /**
   * Over-ridden to generate the factory class source and metadata file 
   * in addition to the JavaBean source.
   *
   * @see #addInstanceCallbacks
   * @see #generateFactory
   * @see #generateMetaData
   * @throws Exception If errors are encountered while generating
   *   the source file.
   */
  @Override
  public void generate() throws Exception
  {
    addInstanceCallbacks();
    generateFactory();
    generateMetaData();
  }

  /**
   * Add <code>InstanceCallbacks</code> support to the JDO JavaBean.
   * Write the modified source code back to the source file.
   *
   * @see ModelGenerator#generate
   * @throws Exception If errors are encountered while writing to
   *   the file.
   */
  protected void addInstanceCallbacks() throws Exception
  {
    super.generate();
    StringBuilder builder = new StringBuilder(
        StringUtilities.fromFile( name + ".java" ) );

    StringBuilder callback = new StringBuilder( 32 );
    Formatter formatter = new Formatter( callback );
    formatter.format( "import javax.jdo.InstanceCallbacks;%n" );

    int index = builder.indexOf( "import" );
    builder.insert( index, callback.toString() );

    String impl = "implements ";
    index = builder.indexOf( impl ) + impl.length();
    builder.insert( index, "InstanceCallbacks, " );
    StringUtilities.toFile( builder.toString(), name + ".java" );
  }

  /**
   * Generate the methods for the JavaBean.
   *
   * @see ModelGenerator#generateMethods
   * @return String The source code for the methods to be included in
   *  the JavaBean.
   */
  @Override
  protected String generateMethods()
  {
    StringBuilder builder = new StringBuilder( super.generateMethods() );
    Formatter formatter = new Formatter( builder );

    formatter.format( "%n  /**%n" );
    formatter.format( "   * Called after the values are loaded from the%n" );
    formatter.format( "   * data store into this instance.%n" );
    formatter.format( "   * InstanceCallbacks method implementation.  No actions required%n" );
    formatter.format( "   * by default.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  public void jdoPostLoad() {}%n%n" );

    formatter.format( "  /**%n" );
    formatter.format( "   * Called before the values are stored from this instance%n" );
    formatter.format( "   * to the data store.%n" );
    formatter.format( "   * InstanceCallbacks method implementation.%n" );
    formatter.format( "   * Pre-calculate {@link #hash} using {@link #hashCode}.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  public void jdoPreStore()%n" );
    formatter.format( "  {%n" );
    formatter.format( "    hash = hashCode();%n" );
    formatter.format( "  }%n%n" );

    formatter.format( "  /**%n" );
    formatter.format( "   * Called before the values in the instance are cleared.%n" );
    formatter.format( "   * InstanceCallbacks method implementation.  No actions required%n" );
    formatter.format( "   * by default.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  public void jdoPreClear() {}%n%n" );

    formatter.format( "  /**%n" );
    formatter.format( "   * Called before the instance is deleted.%n" );
    formatter.format( "   * InstanceCallbacks method implementation.  No actions required%n" );
    formatter.format( "   * by default.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  public void jdoPreDelete() {}%n" );

    return builder.toString();
  }

  /**
   * Generate a <code>Factory</code> class that handles interactions
   * between the JDO JavaBean objects and the JDO datastore.
   *
   * @see #generateFactoryInitialisers
   * @see #generateSaveMethod
   * @see #generateFactoryMethods
   * @throws IOException If errors are encountered while writing the
   *   generated source file.
   */
  protected void generateFactory() throws IOException
  {
    StringBuilder builder = new StringBuilder( 8192 );
    Formatter formatter = new Formatter( builder );
    String factoryName = name + "Factory";

    formatter.format( "package %s;%n%n", source.getPackage().getName() );
    formatter.format( "import java.io.Serializable;%n" );
    formatter.format( "import java.util.ArrayList;%n" );
    formatter.format( "import java.util.Collection;%n" );
    formatter.format( "import java.util.logging.Logger;%n" );

    formatter.format( "%n" );
    formatter.format( "import javax.jdo.JDOHelper;%n" );
    formatter.format( "import javax.jdo.PersistenceManager;%n" );
    formatter.format( "import javax.jdo.Query;%n" );

    formatter.format( "%n" );
    formatter.format( "import com.sptci.jdo.PersistenceManagerFactory;%n" );
    formatter.format( "%n" );

    formatter.format( "/**%n" );
    formatter.format( " * A factory class for abstracting interactions between the%n" );
    formatter.format( 
        " * {@link %s} JDO JavaBean and the JDO data store.%n",
        name );
    formatter.format( " *%n" );
    formatter.format( " * @author %s %s%n", getClass().getName(),
        ( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ssZ" ) ).format( new Date() ) );
    formatter.format( " * @version $Id: JDOModelGenerator.java,v 1.3 2006/02/15 17:23:32 rakesh Exp $%n" );
    formatter.format( " */%n" );
    formatter.format( "public class %s%n", factoryName );
    formatter.format( "  implements Serializable%n" );
    formatter.format( "{%n" );

    builder.append( generateFactoryInitialisers() );
    builder.append( generateSaveMethod() );
    builder.append( generateDeleteMethod() );
    builder.append( generateFactoryMethods() );

    formatter.format( "}%n" );


    StringUtilities.toFile( builder.toString(), factoryName + ".java" );
  }

  /**
   * Generate the initialiser methods for the factory class.
   *
   * @return String The source code for the initialisers.
   */
  protected String generateFactoryInitialisers()
  {
    StringBuilder builder = new StringBuilder( 8192 );
    Formatter formatter = new Formatter( builder );
    String factoryName = name + "Factory";

    formatter.format( "  /**%n" );
    formatter.format( "   * The singleton instance of the class.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  private static final %s singleton =%n",
        factoryName );
    formatter.format( "      new %s();%n%n", factoryName );

    formatter.format( "  /**%n" );
    formatter.format( "   * The logger used to log errors/warnings to.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  private static final Logger logger =%n" );
    formatter.format( "      Logger.getLogger( \"%s.%s\" );%n%n",
        source.getPackage().getName(), factoryName );

    formatter.format( "  /**%n" );
    formatter.format( "   * Default constructor.  Cannot be instantiated.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  protected %s() {}%n%n", factoryName );

    formatter.format( "  /**%n" );
    formatter.format( "   * Return the singleton instance of the class.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @return %s The {@link #singleton} instance.%n",
        factoryName );
    formatter.format( "   */%n" );
    formatter.format( "  public static final %s getInstance()%n",
        factoryName );
    formatter.format( "  {%n" );
    formatter.format( "    return singleton;%n" );
    formatter.format( "  }%n%n" );

    return builder.toString();
  }

  /**
   * Generate the <code>save</code> method that will be used to save
   * a JDO JavaBean to the JDO data store.
   *
   * @return String The source code for the save method.
   */
  protected String generateSaveMethod()
  {
    StringBuilder builder = new StringBuilder( 1024 );
    Formatter formatter = new Formatter( builder );

    String fieldName = name.substring( 0, 1 ).toLowerCase() +
      name.substring( 1 );
    formatter.format( "  /**%n" );
    formatter.format( 
        "   * Save the specified instance of {@link %s}%n",
        name );
    formatter.format( "   * to the JDO data store.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @param %s The instance to persist to%n",
        fieldName );
    formatter.format( "   *   the data store.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  public void save( %s %s )%n", name, fieldName );
    formatter.format( "  {%n" );
    formatter.format( "    PersistenceManager persistenceManager = null;%n" );
    formatter.format( "    boolean active = false;%n%n" );

    formatter.format( "    try%n" );
    formatter.format( "    {%n" );
    formatter.format( "      persistenceManager =%n" );
    formatter.format( "        JDOHelper.getPersistenceManager( %s );%n", fieldName );
    formatter.format( "      if ( persistenceManager == null )%n" );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager =%n" );
    formatter.format( "          PersistenceManagerFactory.getPersistenceManager();%n" );
    formatter.format( "      }%n" );
    formatter.format( "      active = persistenceManager.currentTransaction().isActive();%n%n" );

    formatter.format( "      if ( ! active )%n" );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.currentTransaction().begin();%n" );
    formatter.format( "      }%n%n" );

    formatter.format( "      if ( ! JDOHelper.isPersistent( %s ) )%n",
        fieldName );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.makePersistent( %s );%n", 
        fieldName );
    formatter.format( "      }%n%n" );

    formatter.format( "      if ( ! active )%n" );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.currentTransaction().commit();%n" );
    formatter.format( "      }%n" );

    formatter.format( "    }%n" );
    formatter.format( "    finally%n" );
    formatter.format( "    {%n" );
    formatter.format( "      if ( ! active && persistenceManager.currentTransaction().isActive() )%n" );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.currentTransaction().rollback();%n" );
    formatter.format( "        logger.severe( \"Unknown problem.  Rollling back transaction.\" );%n" );
    formatter.format( "      }%n" );
    formatter.format( "    }%n" );

    formatter.format( "  }%n%n" );

    return builder.toString();
  }

  /**
   * Generate the <code>delete</code> method that will be used to delete
   * a JDO JavaBean to the JDO data store.
   *
   * @return String The source code for the save method.
   */
  protected String generateDeleteMethod()
  {
    StringBuilder builder = new StringBuilder( 1024 );
    Formatter formatter = new Formatter( builder );

    String fieldName = name.substring( 0, 1 ).toLowerCase() +
      name.substring( 1 );
    formatter.format( "  /**%n" );
    formatter.format( 
        "   * Delete the specified instance of {@link %s}%n",
        name );
    formatter.format( "   * from the JDO data store.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @param %s The instance to delete from%n",
        fieldName );
    formatter.format( "   *   the data store.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  public void delete( %s %s )%n", name, fieldName );
    formatter.format( "  {%n" );
    formatter.format( "    PersistenceManager persistenceManager = null;%n" );
    formatter.format( "    boolean active = false;%n%n" );

    formatter.format( "    try%n" );
    formatter.format( "    {%n" );
    formatter.format( "      persistenceManager =%n" );
    formatter.format( "        JDOHelper.getPersistenceManager( %s );%n", fieldName );
    formatter.format( "      active = persistenceManager.currentTransaction().isActive();%n%n" );

    formatter.format( "      if ( ! active )%n" );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.currentTransaction().begin();%n" );
    formatter.format( "      }%n%n" );

    formatter.format( "      if ( JDOHelper.isPersistent( %s ) )%n",
        fieldName );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.deletePersistent( %s );%n", 
        fieldName );
    formatter.format( "      }%n%n" );

    formatter.format( "      if ( ! active )%n" );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.currentTransaction().commit();%n" );
    formatter.format( "      }%n" );

    formatter.format( "    }%n" );
    formatter.format( "    finally%n" );
    formatter.format( "    {%n" );
    formatter.format( "      if ( ! active && persistenceManager.currentTransaction().isActive() )%n" );
    formatter.format( "      {%n" );
    formatter.format( "        persistenceManager.currentTransaction().rollback();%n" );
    formatter.format( "        logger.severe( \"Unknown problem.  Rollling back transaction.\" );%n" );
    formatter.format( "      }%n" );
    formatter.format( "    }%n" );

    formatter.format( "  }%n%n" );

    return builder.toString();
  }

  /**
   * Generate additional factory methods for fetching the JDO
   * JavaBean instances from the JDO data store.
   *
   * @return String The source code for the factory methods.
   */
  protected String generateFactoryMethods()
  {
    StringBuilder builder = new StringBuilder( 2046 );
    Formatter formatter = new Formatter( builder );

    formatter.format( "  /**%n" );
    formatter.format( "   * Return all the instance of {@link %s}%n", 
        name );
    formatter.format( "   * from the JDO data store.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @return Collection An <code>ArrayList</code> of all the instances.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  @SuppressWarnings(value={\"unchecked\"})%n" );
    formatter.format( "  public final Collection<%s> fetchAll()%n",
        name );
    formatter.format( "  {%n" );

    formatter.format( "    Collection<%s> collection = new ArrayList<%s>();%n",
        name, name );
    formatter.format( "    PersistenceManager persistenceManager =%n" );
    formatter.format( "      PersistenceManagerFactory.getPersistenceManager();%n" );
    formatter.format( "    Query query = null;%n" );
    formatter.format( "    try%n" );
    formatter.format( "    {%n" );
    formatter.format( 
        "      query = persistenceManager.newQuery( %s.class );%n",
        name );
    formatter.format( 
        "      collection.addAll( (Collection<%s>) query.execute() );%n",
        name );
    formatter.format( "    }%n" );
    formatter.format( "    finally%n" );
    formatter.format( "    {%n" );
    formatter.format( "      query.closeAll();%n" );
    formatter.format( "    }%n" );
    formatter.format( "%n    return collection;%n%n" );
    formatter.format( "  }%n%n" );

    return builder.toString();
  }

  /**
   * Generate the JDO meta-data file for the JDO JavaBean.
   *
   * <p>Uses JDOM and Jaxen for parsing an existing meta-data file
   * and for creating or updating the file.</p>
   *
   * @see JDOMetaDataGenerator#generate
   * @throws JDOMException If errors are encountered while fetching
   *   the element.
   * @throws IOException If errors are encountered while writing to
   *   the meta-data file.
   */
  protected void generateMetaData()
    throws JDOMException, IOException
  {
    JDOMetaDataGenerator generator = new JDOMetaDataGenerator( name,
        source.getPackage().getName(), fields );
    generator.generate();
  }

  /**
   * Main method.
   */
  public static void main( String[] args )
  {
    if ( args.length != 1 )
    {
      System.err.println( 
          "Usage: java -classpath <classpath> com.sptci.echo2.JDOModelGenerator <UI class>" );
      System.exit( 2 );
    }
    else
    {
      try
      {
        JDOModelGenerator generator = new JDOModelGenerator( args[0] );
        generator.generate();
      }
      catch ( Throwable t )
      {
        t.printStackTrace();
        System.exit( 1 );
      }
    }

    System.exit( 0 );
  }
}
