/*
 * This file is part of the DistSim distributed simulation framework (hibernate-extension)
 * Copyright (C) 2007 Ulf Hermann; 2003-2006 Doug Currie, doug.currie@alum.mit.edu and others
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package brn.distsim.ormapper.class2hbm;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import brn.distsim.ormapper.util.ConstantFieldAccessor;

/**
 * a ReflectedClass is created by MapGenerator instances for every class
 * encountered, from map.addClass(), while chasing the superclass chain, or
 * grokking properties
 *
 * @version 1.x
 * @author <a href="mailto:doug.currie@alum.mit.edu">e</a>
 * @auther <a href="mailto:uhermann@informatik.hu-berlin.de">Ulf Hermann</a>
 */
public class ReflectedClass {

	protected MapGenerator map; // my containing map

	protected StringBuffer buf;// shared buffer provided by MapGenerator

	private Class clazz; // my class

	protected String tableName;

	protected Vector subs; // my ReflectedClass subclasses

	protected List props;// my ReflectedProperty(s) keyed by property name

	protected boolean persistent; // true => persistent class or subclass

	// false => component or serializable

	protected ReflectedClass() {
	}

	public ReflectedClass(MapGenerator map, Class cls) {
		this.clazz = cls;
		this.map = map;
		this.subs = new Vector();
		this.tableName = map.tableNameFor(cls.getName());
		this.persistent = true; // default to component until told otherwise
		map.rClasses.put(cls.getName(), this); // this must happen to avoid
												// infinite
		// regress...
		this.reflect(); // here
	}

	/** used by gui */
	public String getName() {
		return clazz.getName();
	}

	/** used by gui */
	public ReflectedProperty[] getProperties() {
		ReflectedProperty[] rp = new ReflectedProperty[props.size()];
    Iterator iter = props.iterator();
		int i = 0;
		while (iter.hasNext()) {
			rp[i++] = (ReflectedProperty) iter.next();
		}
		return rp;
	}

	/** used by gui */
	public ReflectedClass[] getSubclasses() {
		ReflectedClass[] v = new ReflectedClass[subs.size()];
		subs.copyInto(v);
		return v;
	}

	protected void addReflectedClass(ReflectedClass rc) {
		subs.add(rc);
	}

	/**
	 * has the class been deemed hibernate-persistable? <br>
	 * if not, it may still be useful as a component or may be serializable
	 */
	protected boolean isPersistent() {
		return this.persistent;
	}

	/**
	 * called by MappinByReflection when this class has been deemed
	 * hibernate-persistable
	 */
	protected void setPersistent(boolean p) {
		this.persistent = p;
	}

	/**
	 * does this class have any properties? <br>
	 * this is not reliable until addSuperclassProps() is called once first
	 */
	protected boolean hasProperties() {
		return (props.size() != 0);
	}

	/**
	 * add to this class the properties of all its superclasses
	 */
	protected void addSuperclassProps() {
		// add properties from superclasses
		addSuperclassProps(clazz.getSuperclass());
	} // with helper...

	private void addSuperclassProps(Class supz) {
		if (supz != null) {
			reflectClass(supz);
			addSuperclassProps(supz.getSuperclass()); // recurse
		}
	}

	/**
	 * dump the OR-Mapping XML for a root class and all of its subclasses that
	 * share a table and UID
	 */
	protected void getXML(int level) {
		// dumps in map.buf
		this.buf = map.buf;
		String name = clazz.getName();

		// debug...
		map.emitPrefix(level);
		buf.append("<!-- ").append(name).append(" root -->\n");
		// a root class -- polymorphic style
		map.emitPrefix(level);
		buf.append("<class name=\"").append(name).append("\" table=\"").append(
				tableName).append("\">\n");

    map.emitPrefix(level + 1);
    buf.append("<id ").append("type=\"").append("long").append(
        "\" column=\"").append("primary_key").append("\">\n");
    map.emitPrefix(level + 2);
    buf.append("<generator class=\""+IdGenerator.class.getName()+"\">\n");
//    map.emitPrefix(level + 3);
//    buf.append("<param name=\"simulationId\">").append(simulationId).append("</param>\n");
    map.emitPrefix(level + 2);
    buf.append("</generator>\n");
    map.emitPrefix(level + 1);
    buf.append("</id>\n");

//    map.emitPrefix(level + 1);
//    buf.append("<composite-id>");
//    map.emitPrefix(level + 2);
//    buf.append("<key-property name=\"simulationId\" type=\"int\" access=\""+ConstantFieldAccessor.class.getName()+"\"/>");
//    map.emitPrefix(level + 2);
//    buf.append("<key-property name=\"id\" type=\"int\"/>");
//    map.emitPrefix(level + 1);
//    buf.append("</composite-id>");

//		map.emitPrefix(level + 1);
//		buf.append("<id ").append("type=\"").append("long").append(
//				"\" column=\"").append("primary_key").append("\">\n");
//		map.emitPrefix(level + 2);
//		buf.append("<generator class=\"native\"/>\n");
//		map.emitPrefix(level + 1);
//		buf.append("</id>\n");
		// special properties for identifying simulation, origin and class of
		// the result
		map.emitPrefix(level + 1);
		buf.append("<property name=\"subclass\" type=\"string\" access=\""+ConstantFieldAccessor.class.getName()+"\"/>\n");
		map.emitPrefix(level + 1);
		buf.append("<property name=\"simulationId\" type=\"int\" access=\""+ConstantFieldAccessor.class.getName()+"\"/>\n");
		map.emitPrefix(level + 1);
		buf.append("<property name=\"entity\" type=\"string\" access=\""+ConstantFieldAccessor.class.getName()+"\"/>\n");

    // now the properties already classified by heuristic
    Iterator iter = props.iterator();
    while (iter.hasNext()) {
			ReflectedProperty prop = (ReflectedProperty) iter.next();
			if (prop.isUid)
				continue; // done above
			// we're root and top component level
			prop.getXML(level + 1, buf);
			map.cycleBuster = new HashSet();
		}
		map.emitPrefix(level);
		buf.append("</class>\n");
		// now the subclasses
		int len = subs.size();
		for (int i = 0; i < len; i++) {
			ReflectedClass rsc = (ReflectedClass) subs.get(i);
			rsc.getXMLasSubclass(level);
		}
	}

	/**
	 * dump the OR-Mapping XML for a non-root class and all of its subclasses
	 * that share its table and UID
	 */
	protected void getXMLasSubclass(int level) {
		// dumps in map.buf
		this.buf = map.buf;
		String name = clazz.getName();
		String superclass = clazz.getSuperclass().getName();
		// debug...
		map.emitPrefix(level);
		;
		buf.append("<!-- ").append(name).append(" -->\n");
		// a sub class -- polymorphic style
		map.emitPrefix(level);
		;
		buf.append("<joined-subclass name=\"").append(name).append(
				"\" table=\"").append(tableName).append("\" extends=\"")
				.append(superclass).append("\">\n");
		map.emitPrefix(level + 1);
		buf.append("<key column=\"primary_key\" on-delete=\"cascade\"/>\n");
		// now the properties already classified by heuristic
    Iterator iter = props.iterator();
    while (iter.hasNext()) {
			ReflectedProperty prop = (ReflectedProperty) iter.next();
			if (prop.isUid)
				continue; // done above
			prop.getXML(level + 1, buf);
		}
		map.emitPrefix(level);
		buf.append("</joined-subclass>\n");
		// now the subclasses
		int len = subs.size();
		for (int i = 0; i < len; i++) {
			ReflectedClass rsc = (ReflectedClass) subs.get(i);
			rsc.getXMLasSubclass(level);
		}
	}

	/**
	 * dump the OR-Mapping XML for a class used as a component
	 *
	 * @param propName
	 *            the property name of the property containing this component
	 */
	protected void getXMLasComponent(int level, String propName) {
		// dumps in buf
		this.buf = map.buf;
		String name = clazz.getName();

		// a component
		map.emitPrefix(level);
		;
		buf.append("<component name=\"").append(propName).append("\" class=\"")
				.append(name).append("\">\n");
		// now the properties already classified by heuristic
    Iterator iter = props.iterator();
    while (iter.hasNext()) {
      ReflectedProperty prop = (ReflectedProperty) iter.next();
			prop.getXML(level + 1, buf);
		}
		// NOT the subclasses
		map.emitPrefix(level);
		buf.append("</component>\n");
	}

	protected void getXMLasComposite(int level) {
		// dumps in buf
		this.buf = map.buf;
		String name = clazz.getName();
		// a composite-element
		map.emitPrefix(level);
		;
		buf.append("<composite-element class=\"").append(name).append("\">\n");
		// now the properties already classified by heuristic
    Iterator iter = props.iterator();
    while (iter.hasNext()) {
      ReflectedProperty prop = (ReflectedProperty) iter.next();
			// call a restricted form of getXML that disallows inline
			// collections and uses composite-element instead of component
			prop.getXMLinComposite(level, buf);
		}
		// NOT the subclasses
		map.emitPrefix(level);
		;
		buf.append("</composite-element>\n");
	}

	protected void getXMLasMany2Many(int level) {
		// dumps in buf
		this.buf = map.buf;
		String name = clazz.getName();
		// a many-to-many
		map.emitPrefix(level);
		buf.append("<many-to-many class=\"").append(name)
				.append("\" column=\"").append(map.columnNameFor(name)).append(
						"\"/>\n");
	}

	/**
	 * called by the constructor
	 */
	protected void reflect() {
		props = new ArrayList();
		reflectClass(clazz);
	}

	/**
	 * called to get the properties of a class, or add properties from a
	 * superclass
	 */
	private void reflectClass(Class cls) {
		// empty getprops and setprops if a getter and setter pair
		// should come from one class and not a class and its superclass
		try {
			Field[] fields = cls.getDeclaredFields();
			for (int i = 0; i < fields.length; ++i) {
				Field field = fields[i];
        // static fields aren't mapped
        if (Modifier.isStatic(field.getModifiers()))
					continue;
				String name = field.getName();
				Class type = field.getType();
				ReflectedProperty prop = map.makeProperty(cls.getName(), name,
						type);
				if (prop != null)
					props.add(prop);
			}
		} catch (SecurityException e) {
			// ignore!?
		}
	}

  public String getSuperclass() {
    return clazz.getSuperclass().getName();
  }

}
