package com.sptci;

import java.lang.reflect.Field;

import java.util.Formatter;
import java.util.Map;
import java.util.HashSet;
import java.util.TreeMap;

/**
 * An abstract code generator.  Generates the code for common methods
 * that are implemented in source code.
 *
 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
 * @author Rakesh Vidyadharan 2006-02-07
 * @version $Id: CodeGenerator.java,v 1.2 2006/02/09 16:09:13 rakesh Exp $
 */
public abstract class CodeGenerator
{
  /**
   * The name of the class that is being generated.
   */
  protected String name;

  /**
   * A <code>Map</code> of <code>fields</code> for the generated class.
   * The name of the field is stored as the <code>key</code> and the
   * type of the field as the <code>value</code>.
   */
  protected Map<String, String> fields;

  /**
   * A <code>HashSet</code> containing the primitive types defined
   * for Java.
   */
  protected HashSet<String> primitives;

  /**
   * Default constructor.  Initialises code generator containers.
   */
  public CodeGenerator()
  {
    fields = new TreeMap<String, String>();
    primitives = new HashSet<String>();
    primitives.add( "boolean" );
    primitives.add( "byte" );
    primitives.add( "char" );
    primitives.add( "double" );
    primitives.add( "float" );
    primitives.add( "int" );
    primitives.add( "long" );
    primitives.add( "short" );
  }

  /**
   * The abstract method that will generate the full source code.
   *
   * @throws Exception If errors are encountered while generating
   *   the source file.
   */
  public abstract void generate() throws Exception;

  /**
   * Generate the default fields for the class.  Sub-classes must
   * over-ride this method, while usually using the results of this
   * method to enhance their output.
   *
   * @return String The default fields to be added to each source file.
   */
  protected String generateFields()
  {
    StringBuilder builder = new StringBuilder( 256 );
    Formatter formatter = new Formatter( builder );

    formatter.format( "  /**%n" );
    formatter.format( "   * The default value to use as the base for hashCode.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  protected static final int HASH = 7;%n%n" );

    formatter.format( "  /**%n" );
    formatter.format( "   * The field used to store pre-computed hashCode.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  protected int hash = HASH;%n%n" );

    return builder.toString();
  }

  /**
   * Generate the <code>toString</code> method for the JavaBean.
   * Generate an XML representation of the class and its fields.
   *
   * @return String The default <code>toString</code> method
   *   implementation.
   */
  protected String generateToStringMethod()
  {
    StringBuilder builder = new StringBuilder( 2048 );
    Formatter formatter = new Formatter( builder );

    formatter.format( "  /**%n" );
    formatter.format( "   * Return a String representation of the fields of this class.%n" );
    formatter.format( "   * The String representation is returned as an XML document.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @return String An XML representation of the class fields.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  @Override%n" );
    formatter.format( "  public String toString()%n" );
    formatter.format( "  {%n" );

    formatter.format( "    StringBuilder builder = new StringBuilder( 4096 );%n" );
    formatter.format( "    Formatter formatter = new Formatter( builder );%n" );

    formatter.format( "    formatter.format( \"<%s package='%%s'>%%n\", getClass().getPackage().getName() );%n%n", name );

    for ( Map.Entry<String, String> entry : fields.entrySet() )
    {
      formatter.format( "    formatter.format( \"<%s type='%s'>%%n\" );%n",
          entry.getKey(), 
          entry.getValue().replaceAll( "<", "&lt;" ).replaceAll( ">", "&gt;" ) );

      if ( entry.getValue().startsWith( "Map<" ) )
      {
        formatter.format( 
            "    for ( Map.Entry<%s> entry : %s.entrySet() )%n",
            entry.getValue().substring( 
              entry.getValue().indexOf( "<" ) + 1,
              entry.getValue().indexOf( ">" ) ), entry.getKey() );
        formatter.format( "    {%n" );
        formatter.format( "      formatter.format( \"<key>%%n%%s%%n</key>%%n\", entry.getKey() );%n" );
        formatter.format( "      formatter.format( \"<value>%%n%%s%%n</value>%%n\", entry.getValue() );%n" );
        formatter.format( "    }%n" );
      }
      else if ( entry.getValue().startsWith( "List<" ) )
      {
        formatter.format( 
            "    for ( %s entry : %s )%n",
            entry.getValue().substring( 
              entry.getValue().indexOf( "<" ) + 1,
              entry.getValue().indexOf( ">" ) ), entry.getKey() );
        formatter.format( "    {%n" );
        formatter.format( "      formatter.format( \"<entry>%%n%%s%%n</entry>%%n\", entry );%n" );
        formatter.format( "    }%n" );
      }
      else
      {
        formatter.format( "    formatter.format( \"%%s%%n\", %s );%n",
            entry.getKey() );
      }

      formatter.format( "    formatter.format( \"</%s>%%n\" );%n%n",
          entry.getKey() );
    }

    formatter.format( "    formatter.format( \"</%s>%%n\" );%n", name );

    formatter.format( "    return builder.toString();%n" );
    formatter.format( "  }%n" );

    return builder.toString();
  }

  /**
   * Generate the <code>equals</code> method for the JavaBean.
   *
   * @return String The default <code>equals</code> method 
   *   implementation.
   */
  protected String generateEqualsMethod()
  {
    StringBuilder builder = new StringBuilder( 1024 );
    Formatter formatter = new Formatter( builder );

    formatter.format( "  /**%n" );
    formatter.format( "   * Compare the specified object with this object for equality.%n" );
    formatter.format( "   * Returns <code>true</code> if the specified object is of the%n" );
    formatter.format( "   * same type as this  object, and the values of the fields%n" );
    formatter.format( "   * are equal.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @param object The object that is to compared with this object.%n" );
    formatter.format( "   * @return boolean Returns <code>true</code> if the values of%n" );
    formatter.format( "   *   the class fields are the same.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  @Override%n" );
    formatter.format( "  public boolean equals( Object object )%n" );
    formatter.format( "  {%n" );
    formatter.format( "    if ( this == object ) return true;%n%n" );
    formatter.format( "    boolean value = false;%n" );
    formatter.format( "    if ( ( object != null ) &&%n" );
    formatter.format( "        ( getClass() == object.getClass() ) )%n" );
    formatter.format( "    {%n" );
    formatter.format( "      %s obj = (%s) object;%n%n", name, name );
    formatter.format( "      value = ( hashCode() == obj.hashCode() );%n" );

    int count = 0;
    for ( Map.Entry<String, String> entry : fields.entrySet() )
    {
      formatter.format( "      value = value && " );

      if ( primitives.contains( entry.getValue() ) )
      {
        formatter.format( " ( %s == obj.%s );%n", 
            entry.getKey(), entry.getKey() );
      }
      else
      {
        formatter.format( " ( ( %s == obj.%s ) ||%n",
            entry.getKey(), entry.getKey() );
        formatter.format( "          ( %s != null &&%n", entry.getKey() );
        formatter.format( "            %s.equals( obj.%s ) ) );%n",
          entry.getKey(), entry.getKey() );
      }

      ++count;
    }

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

    return builder.toString();
  }

  /**
   * Generate the <code>equals</code> method for the JavaBean.
   *
   * @return String The default <code>hashCode</code> method 
   *   implementation.
   */
  protected String generateHashCodeMethod()
  {
    StringBuilder builder = new StringBuilder( 1024 );
    Formatter formatter = new Formatter( builder );

    formatter.format( "  /**%n" );
    formatter.format( "   * Return a hash code for this object.  Returns a hash code%n" );
    formatter.format( "   * computed from the class fields.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @return int The hashCode value.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  @Override%n" );
    formatter.format( "  public int hashCode()%n" );
    formatter.format( "  {%n" );
    formatter.format( "    if ( hash == HASH )%n" );
    formatter.format( "    {%n" );
    for ( Map.Entry<String, String> entry : fields.entrySet() )
    {
      if ( primitives.contains( entry.getValue() ) )
      {
        if ( entry.getValue().equals( "boolean" ) )
        {
          formatter.format( "      hash = ( 31 * hash ) +%n" );
          formatter.format( "        ( ( %s ) ? 1 : 0;%n",
              entry.getKey() );
        }
        else
        {
          formatter.format( 
              "      hash = ( 31 * hash ) + ( (int) %s );%n", 
              entry.getKey() );
        }
      }
      else
      {
        formatter.format( "      hash = ( 31 * hash ) +%n" );
        formatter.format( "        ( ( %s == null ) ? 0 : %s.hashCode() );%n",
            entry.getKey(), entry.getKey() );
      }
    }
    formatter.format( "    }%n" );

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

    return builder.toString();
  }

  /**
   * Generate the <code>compareTo</code> method for the JavaBean.
   *
   * @return String The default <code>compareTo</code> method
   *   implementation.
   */
  protected String generateCompareToMethod()
  {
    StringBuilder builder = new StringBuilder( 1024 );
    Formatter formatter = new Formatter( builder );

    formatter.format( "  /**%n" );
    formatter.format( "   * Implementation of the <code>Comparable</code> interface.%n" );
    formatter.format( "   * Compares this object with the specified object for order. Returns%n" );
    formatter.format( "   * a negative integer, zero, or a positive integer as this object is%n" );
    formatter.format( "   * less than, equal to, or greater than the specified object.%n" );
    formatter.format( "   * The comparison is done by comparing the {@link #hashCode()} values%n" );
    formatter.format( "   * of the objects.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @param object - The object with which this class is to%n" );
    formatter.format( "   *   be compared.  No class type checking is done.%n" );
    formatter.format( "   * @return int - A negative integer, zero, or a positive integer as%n" );
    formatter.format( "   *   this object is less than, equal to, or greater than the%n" );
    formatter.format( "   *   specified object.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  public int compareTo( %s object )%n", name );
    formatter.format( "  {%n" );
    formatter.format( "    return ( this.hashCode() - object.hashCode() );%n" );
    formatter.format( "  }%n%n" );

    return builder.toString();
  }

  /**
   * Generate the clone method for the JavaBean.
   *
   * @return String The default <code>clone</code> method implementation.
   */
  protected String generateCloneMethod()
  {
    StringBuilder builder = new StringBuilder( 1024 );
    Formatter formatter = new Formatter( builder );

    formatter.format( "  /**%n" );
    formatter.format( "   * Creates and returns a copy of this object.  Implementation of%n" );
    formatter.format( "   * the <code>Cloneable</code> interface.  No special actions are%n" );
    formatter.format( "   * performed.  This method simply allows public access to the%n" );
    formatter.format( "   * <code>Object.clone</code> method.%n" );
    formatter.format( "   *%n" );
    formatter.format( "   * @return Object A clone of this instance.%n" );
    formatter.format( "   * @throws CloneNotSupportedException If the super-class implementation%n" );
    formatter.format( "   *   throws an error.%n" );
    formatter.format( "   */%n" );
    formatter.format( "  @Override%n" );
    formatter.format( "  public Object clone() throws CloneNotSupportedException%n" );
    formatter.format( "  {%n" );
    formatter.format( "    return super.clone();%n" );
    formatter.format( "  }%n%n" );

    return builder.toString();
  }

  /**
   * Generate the accessor and mutator methods for the JavaBean.
   *
   * @return String The default accessor and mutator methods for
   *   the {@link #fields}.
   */
  protected String generateBeanMethods()
  {
    StringBuilder builder = new StringBuilder( 4096 );
    Formatter formatter = new Formatter( builder );
    for ( Map.Entry<String, String> entry : fields.entrySet() )
    {
      formatter.format( "%n  /**%n" );
      formatter.format( 
          "   * Returns the value of {@link #%s}.%n",
          entry.getKey() );
      formatter.format( "   *%n" );
      formatter.format( "   * @return %s The value of %s.%n",
          entry.getValue(), entry.getKey() );
      formatter.format( "   */%n" );
      formatter.format( "  public final %s get%s%s()%n",
          entry.getValue(), 
          entry.getKey().substring( 0, 1 ).toUpperCase(),
          entry.getKey().substring( 1 ) );
      formatter.format( "  {%n" );
      formatter.format( "    return %s;%n", entry.getKey() );
      formatter.format( "  }%n%n" );

      formatter.format( "  /**%n" );
      formatter.format( 
          "   * Sets the value of {@link #%s}.  Resets {@link #hash}.%n",
          entry.getKey() );
      formatter.format( "   *%n" );
      formatter.format( "   * @param %s The value to set.%n",
          entry.getKey() );
      formatter.format( "   */%n" );
      formatter.format( "  public final void set%s%s( %s %s )%n",
          entry.getKey().substring( 0, 1 ).toUpperCase(),
          entry.getKey().substring( 1 ),
          entry.getValue(), entry.getKey() );
      formatter.format( "  {%n" );
      formatter.format( "    %s oldValue = this.%s;%n",
          entry.getValue(), entry.getKey() );
      formatter.format( "    this.%s = %s;%n", 
          entry.getKey(), entry.getKey() );
      formatter.format( "    hash = HASH;%n" );
      formatter.format( "  }%n" );
    }

    return builder.toString();
  }
}
