package com.sptci.echo2;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.List;
import java.util.Map;

import org.jdom.Attribute;
import org.jdom.DocType;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;

/**
 * A JDO meta-data generator.  This is a generic generator that does
 * not apply any JDO vendor specific extensions.  Sub-classes may be
 * developed that apply vendor specific extensions.
 *
 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
 * @author Rakesh Vidyadharan 2006-02-08
 * @version $Id: JDOMetaDataGenerator.java,v 1.4 2006/02/15 17:22:56 rakesh Exp $
 */
public class JDOMetaDataGenerator
{
  /**
   * The name of the class for which the meta-data is to be generated.
   */
  protected String name;

  /**
   * The name of the package to which the class belongs.
   */
  protected String packageName;

  /**
   * The fields available in the class for which meta-data is to be
   * generated.
   */
  protected Map<String, String> fields;

  /**
   * Default constructor.  No special actions required.
   */
  protected JDOMetaDataGenerator() {}

  /**
   * Create a new instance of the class using the specified values.
   *
   * @param name The {@link #name} value to set.
   * @param packageName The {@link #packageName} value to set.
   * @param fields The {@link #fields} value to set.
   */
  public JDOMetaDataGenerator ( String name, String packageName,
      Map<String, String> fields )
  {
    super();
    this.name = name;
    this.packageName = packageName;
    this.fields = fields;
  }

  /**
   * Generate the JDO meta-data file for a JDO JavaBean.
   *
   * <p>Uses JDOM and Jaxen for parsing an existing meta-data file
   * and for creating or updating the file.</p>
   *
   * @see #updateFile
   * @see #generateFile
   * @throws JDOMException If errors are encountered while fetching
   *   the element.
   * @throws IOException If errors are encountered while writing to
   *   the meta-data file.
   */
  public void generate() throws JDOMException, IOException
  {
    File file = new File( "package.jdo" );

    if ( file.exists() )
    {
      updateFile( file );
    }
    else
    {
      generateFile( file );
    }
  }

  /**
   * Generate a new meta-data file with the meta-data for the {@link
   * #name} JDO object.
   *
   * @see #writeFile
   * @param file The file to 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 generateFile( File file )
    throws JDOMException, IOException
  {
    DocType docType = new DocType( 
        "jdo", 
        "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 1.0//EN",
        "http://java.sun.com/dtd/jdo_1_0.dtd" );

    Document document = new Document( new Element( "jdo" ), docType );
    document.getRootElement().addContent( generateMetaData() );
    document.getRootElement().addContent( generateEchoPackage() );
    writeFile ( file, document );
  }

  /**
   * Generate the meta-data elements and attributes for the JDO object
   * represented by {@link #name}.
   *
   * @see #generateSourcePackage
   * @see #generateSource
   * @return Element The element that represents the JDO object.
   */
  protected Element generateMetaData()
  {
    Element element = generateSourcePackage();
    element.addContent( generateSource() );

    return element;
  }

  /**
   * Generate the package ({@link #packageName}) element for the class 
   * represented by {@link #name}.
   *
   * @return Element The element representing the package.
   */
  protected Element generateSourcePackage()
  {
    Element element = new Element( "package" );
    element.setAttribute( "name", packageName );

    return element;
  }

  /**
   * Generate the element for the class represented by {@link #name}.
   *
   * @return Element The element representing the class.
   */
  protected Element generateSource()
  {
    Element element = new Element( "class" );
    element.setAttribute( "name", name );

    Element hash = new Element( "field" );
    hash.setAttribute( "name", "hash" );
    element.addContent( hash );

    for ( Map.Entry<String, String> entry : fields.entrySet() )
    {
      Element field = new Element( "field" );
      field.setAttribute( "name", entry.getKey() );
      element.addContent( field );

      if ( entry.getValue().contains( "List<ListItem>" ) )
      {
        Element child = new Element( "collection" );
        child.setAttribute( "element-type", "com.sptci.echo2.ListItem" );
        field.addContent( child );
      }
      if ( entry.getValue().contains( "Map<String, Boolean>" ) )
      {
        Element child = new Element( "map" );
        child.setAttribute( "key-type", "java.lang.String" );
        child.setAttribute( "value-type", "java.lang.Boolean" );
        field.addContent( child );
      }
    }

    return element;
  }

  /**
   * Generate the meta-data elements and attributes for the
   * <code>com.sptci.echo2</code> package.
   *
   * @see #generateListItem
   * @return Element The element that represents the meta-data for
   *   the package.
   */
  protected Element generateEchoPackage()
  {
    Element element = new Element( "package" );
    element.setAttribute( "name", "com.sptci.echo2" );
    element.addContent( generateListItem() );

    return element;
  }

  /**
   * Write the specified XML document to the specified file.
   *
   * @param file The file to write to.
   * @param document The document to write.
   * @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 writeFile( File file, Document document )
    throws JDOMException, IOException
  {
    FileOutputStream out = new FileOutputStream( file );
    Format format = Format.getPrettyFormat();
    format.setLineSeparator( System.getProperty( "line.separator" ) );
    format.setIndent( "  " );

    XMLOutputter outputter = new XMLOutputter( format ); 
    outputter.output( document, out );
    out.flush();
    out.close();
  }

  /**
   * Add the meta-data for the {@link #name} JDO object to the
   * existing meta-data file.
   *
   * @see #fetchSourcePackage
   * @see #generateMetaData
   * @see #fetchSource
   * @see #generateSource
   * @see #fetchEchoPackage
   * @see #fetchListItem
   * @see #generateEchoPackage
   * @see #generateListItem
   * @see #writeFile
   * @param file The file to update.
   * @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 updateFile( File file )
    throws JDOMException, IOException
  {
    SAXBuilder builder = new SAXBuilder();
    Document document = builder.build( file );

    Element sourcePackage = fetchSourcePackage( document );
    if ( sourcePackage == null )
    {
      document.getRootElement().addContent( generateMetaData() );
    }
    else if ( fetchSource( document ) == null )
    {
      sourcePackage.addContent( generateSource() );
    }
    
    Element echo = fetchEchoPackage( document );
    if ( echo == null )
    {
      document.getRootElement().addContent( generateEchoPackage() );
    }
    else
    {
      if ( fetchListItem( document ) == null )
      {
        echo.addContent( generateListItem() );
      }
    }

    writeFile( file, document );
  }

  /**
   * Fetch the element (if it exists) for the package definition for
   * {@link #packageName}.
   *
   * @param document The document from which to fetch the element.
   * @return Element The appropriate element.
   * @throws JDOMException If errors are encountered while fetching
   *   the element.
   */
  protected Element fetchSourcePackage( Document document )
    throws JDOMException
  {
    XPath xpath = XPath.newInstance( 
        "/jdo/package[@name = '" + packageName + "']" );
    return ( (Element) xpath.selectSingleNode( document ) );
  }

  /**
   * Fetch the element (if it exists) that represents the class
   * representing {@link #name}.
   *
   * @param document The document from which to fetch the element.
   * @return Element The appropriate element.
   * @throws JDOMException If errors are encountered while fetching
   *   the element.
   */
  protected Element fetchSource( Document document )
    throws JDOMException
  {
    XPath xpath = XPath.newInstance( 
        "/jdo/package[@name = '" + packageName + "']/class[@name = '" + name + "']" );
    return ( (Element) xpath.selectSingleNode( document ) );
  }

  /**
   * Fetch the element (if it exists) for the package definition for
   * <code>com.sptci.echo2</code>.
   *
   * @param document The document from which to fetch the element.
   * @return Element The appropriate element.
   * @throws JDOMException If errors are encountered while fetching
   *   the element.
   */
  protected Element fetchEchoPackage( Document document )
    throws JDOMException
  {
    XPath xpath = XPath.newInstance( 
        "/jdo/package[@name = 'com.sptci.echo2']" );
    return ( (Element) xpath.selectSingleNode( document ) );
  }

  /**
   * Fetch the element (if it exists) for the class definition for
   * <code>com.sptci.echo2.ListItem</code>.
   *
   * @param document The document from which to fetch the element.
   * @return Element The appropriate element.
   * @throws JDOMException If errors are encountered while fetching
   *   the element.
   */
  protected Element fetchListItem( Document document )
    throws JDOMException
  {
    XPath xpath = XPath.newInstance( 
        "/jdo/package[@name = 'com.sptci.echo2']/class[@name = 'ListItem']" );
    return ( (Element) xpath.selectSingleNode( document ) );
  }

  /**
   * Generate the meta-data for the {@link ListItem} class.
   *
   * @return Element The element that represents the meta-data for
   *   the class.
   */
  protected Element generateListItem()
  {
    Element element = new Element( "class" );
    element.setAttribute( "name", "ListItem" );

    Element field1 = new Element( "field" );
    field1.setAttribute( "name", "value" );
    field1.setAttribute( "null-value", "exception" );
    element.addContent( field1 );

    Element field2 = new Element( "field" );
    field2.setAttribute( "name", "selected" );
    field2.setAttribute( "null-value", "exception" );
    element.addContent( field2 );

    return element;
  }
}
