package tutorial.jdo;

import java.lang.reflect.*;
import java.util.*;
import javax.jdo.*;

/**
 * Provides some maintenance methods for adding, removing, and analyzing
 * the animals in the data store.
 */
public class AnimalMaintenance {

    /**
     * Return a list of animals that match the specified query filter.
     *
     * @param filter the JDO filter to apply to the query
     * @param cls the class of animal to query on
     * @param pm the PersistenceManager to obtain the query from
     */
    public static List getAnimals(String filter, Class cls, 
        PersistenceManager pm) {
        // Execute a query for the specified class and filter.
        Query query = pm.newQuery(cls, filter);
        return (List) query.execute();
    }

    /**
     * Performs the actual JDO work of putting <code>object</code>
     * into the data store.
     *
     * @param object the object to persist in the data store
     */
    public static void persistObject(Object object) {
        // Get a PersistenceManagerFactory and PersistenceManager.
        PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory 
            ("META-INF/jdo.properties");
        PersistenceManager pm = pmf.getPersistenceManager();

        // Obtain a transaction and mark the beginning
        // of the unit of work boundary.
        Transaction transaction = pm.currentTransaction();
        transaction.begin();

        pm.makePersistent(object);

        // Mark the end of the unit of work boundary,
        // and record all inserts in the database.
        transaction.commit();

        System.out.println("Added " + object);

        // Close the PersistenceManager and PersistenceManagerFactory.
        pm.close();
        pmf.close();
    }

    /**
     * Performs the actual JDO work of removing <code>objects</code>
     * from the data store.
     *
     * @param objects the objects to persist in the data store
     * @param pm the PersistenceManager to delete with
     */
    public static void deleteObjects(Collection objects, 
        PersistenceManager pm) {
        // Obtain a transaction and mark the beginning of the
        // unit of work boundary.
        Transaction transaction = pm.currentTransaction();
        transaction.begin();

        for (Iterator iter = objects.iterator(); iter.hasNext(); )
            System.out.println("Removed animal: " + iter.next());

        // This method removes the objects in 'objects' from the data store. 
        pm.deletePersistentAll(objects);

        // Mark the end of the unit of work boundary, and record all
        // deletes in the database.
        transaction.commit();
    }

    ///////////////////////////////////////////////////////////////
    // Methods below here are provided by BEA, and do not 
    // need to be altered. Feel free to examine the code below, 
    // but for the purposes of this tutorial, there is no need to
    // read and understand everything that they do.                
    ///////////////////////////////////////////////////////////////

    /**
     * Uses reflection to add the animal specified by <code>cls</code>,
     * <code>name</code>, and <code>arg</code>. Assumes that there is a 
     * two-arg constructor whose first argument is a string name, and whose
     * second argument is some sort of primitive or string.
     *
     * @param cls the type of animal to add; one of Dog, Rabbit, or Snake
     * @param name the name of the animal to add
     * @param arg the numeric arg to be passed to the constructor.
     * this will be translated into the appropriate type
     * for the class specified, as determined by reflection
     * on the constructors
     */
    public static void add(Class cls, String name, String arg)
        throws IllegalArgumentException {
        try {
            Constructor[] constructors = cls.getDeclaredConstructors();
            for (int i = 0; i < constructors.length; i++) {
                // Find an appropriate constructor.
                Class[] paramTypes = constructors[i].getParameterTypes();
                if (paramTypes.length == 2 && paramTypes[0] == String.class) {
                    Object[] params = new Object[] { 
                        name, getPrimitiveWrapper(arg, paramTypes[1]),
                    };
    
                    Object newObject = constructors[i].newInstance(params);
                    persistObject(newObject);
                }
            }
        } catch (Exception e) {
            System.out.println("Exception creating " + cls + ": " + e);
        }
    }

    /**
     * Attempt to convert <code>arg</code> to a primite object wrapper
     * of class <code>cls</code>.
     */
    private static Object getPrimitiveWrapper(String arg, Class cls) {
        if (cls.equals(Boolean.class) || cls.equals(Boolean.TYPE))
            return Boolean.valueOf(arg);
        if (cls.equals(Byte.class) || cls.equals(Byte.TYPE))
            return Byte.valueOf(arg);
        if (cls.equals(Character.class) || cls.equals(Character.TYPE))
            return new Character(arg.charAt (0));
        if (cls.equals(Short.class) || cls.equals(Short.TYPE))
            return Short.valueOf(arg);
        if (cls.equals(Integer.class) || cls.equals(Integer.TYPE))
            return Integer.valueOf(arg);
        if (cls.equals(Long.class) || cls.equals(Long.TYPE))
            return Long.valueOf(arg);
        if (cls.equals(Float.class) || cls.equals(Float.TYPE))
            return Float.valueOf(arg);
        if (cls.equals(Double.class) || cls.equals(Double.TYPE))
            return Double.valueOf(arg);
        return null;
    }

    /**
     * Removes all animals that match <code>query</code>.
     *
     * @param query the filter to use when finding animals to remove
     * @param cls the class of animal to query on
     */
    public static void remove(String query, Class cls) {
        // don't allow removing all animals
        if (query.trim().length() == 0) {
            System.out.println("Error: cannot remove all animals. Please "
                + "provide a query.");
            return;
        }

        PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory 
            ("META-INF/jdo.properties");
        PersistenceManager pm = pmf.getPersistenceManager();

        List animals = getAnimals(query, cls, pm);
        if (animals.isEmpty()) {
            System.out.println("No animal matching '" + query
                + "' found in persistence manager.");
        } else
            deleteObjects(animals, pm);
        pm.close();
        pmf.close();
    }

    /**
     * Prints out a description of all the animals that match
     * <code>query</code>.
     *
     * @param query the filter to use when finding animals to list
     * @param cls the class of animal to query on
     * @param detailed if <code>true</code>, display details about
     * each animal identified by <code>query</code>;
     * else, print out terse info only
     */
    public static void list(String query, Class cls, boolean detailed) {
        PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory 
            ("META-INF/jdo.properties");
        PersistenceManager pm = pmf.getPersistenceManager();
        Collection animals = getAnimals(query, cls, pm);
        if (animals.size() == 0) {
            System.out.println("No animal matching '" + query
                + "' found in persistence manager.");
        } else {
            for (Iterator iter = animals.iterator(); iter.hasNext();) {
                Animal animal = (Animal) iter.next();
                System.out.println(animal.toString(detailed));
            }
        }
        pm.close();
        pmf.close();
    }

    public static void main(String[] args)
        throws Exception {
        try {
            if ((args.length == 2 || args.length == 3) 
                && args[0].equals("list")) {
                String query = "";
                if (args.length == 3)
                    query = args[2];
                list(query, getAnimalSubclass(args[1]), false);
                return;
            }
            if (args.length == 3 && args[0].equals("details")) {
                list(args[2], getAnimalSubclass(args[1]), true);
                return;
            }
            if (args.length == 3 && args[0].equals("remove")) {
                remove(args[2], getAnimalSubclass(args[1]));
                return;
            }
            if (args.length == 4 && args[0].equals("add")) {
                add(getAnimalSubclass(args[1]), args[2], args[3]);
                return;
            }
        } catch (ClassNotFoundException cnfe) {
            System.out.println("Error: tutorial.jdo." + args[1] + " does not"
                + " exist. Ensure that this class is compiled, and retry.");
        }
        
        // If we get here, something went wrong.
        System.out.println("Usage:");
        System.out.println("    java tutorial.jdo.AnimalMaintenance list"
            + " 'class' ['query']");
        System.out.println("    java tutorial.jdo.AnimalMaintenance details"
            + " 'class' 'query'");
        System.out.println("    java tutorial.jdo.AnimalMaintenance remove"
            + " 'class' 'query'");
        System.out.println("    java tutorial.jdo.AnimalMaintenance add Dog"
            + " 'name' 'price'");
        System.out.println("    java tutorial.jdo.AnimalMaintenance add Snake"
            + " 'name' 'length'");
        System.out.println("    java tutorial.jdo.AnimalMaintenance add Rabbit"
            + " 'name' 'isFemale'");
        System.out.println();
        System.out.println("    'query' is a JDO query string identifying "
            + "the animals to act upon");
        System.out.println("    'class' is the Animal subclass to query on.");
    }

    /**
     * Looks up the class specified by <code>clsName</code>. This class must 
     * be one of Dog, Rabbit, Snake, and Animal.
     */
    private static Class getAnimalSubclass(String clsName)
        throws ClassNotFoundException {
        if (!clsName.startsWith("tutorial.jdo."))
            clsName = "tutorial.jdo." + clsName;

        if (!clsName.equals("tutorial.jdo.Dog") 
            && !clsName.equals("tutorial.jdo.Rabbit") 
            && !clsName.equals("tutorial.jdo.Snake") 
            && !clsName.equals("tutorial.jdo.Animal")) {
            throw new IllegalArgumentException 
                ("Error: " + clsName + " is not a legal type of animal. "
                 + "Animal types are limited to \"Dog\", \"Rabbit\", "
                 + "\"Animal\", and \"Snake\".");
        }

        return Class.forName(clsName);
    }
}