// $Id: Model.java,v 1.8 2002/08/13 23:12:52 mdean Exp $


package org.daml.java2daml;


public class Model
{
    /**
     * underlying representation
     */
    com.hp.hpl.mesa.rdf.jena.model.Model model;
    /**
     * objects which have been added to this model already.
     * Maps from Object to Resource
     */
    java.util.Hashtable objects = new java.util.Hashtable();

    PropertyList properties = new PropertyList();
    boolean addedProperties = false;

    /**
     * URI prefixed to Classes (excluding trailing "#")
     */
    String ontologyURI = "";

    /**
     * URI prefixed to XML Schema datatypes
     */
    static String xmlURI = "http://www.w3.org/2000/10/XMLSchema#";

    boolean firstClass = true;

    public Model()
    {
	model = new com.hp.hpl.mesa.rdf.jena.mem.ModelMem();
    }

    /**
     * return the corresponding XML Schema type, or null if none.
     * XXX -- perhaps now more general
     */
    public Object xmlSchemaType(Class cl)
	throws Exception
    {
	if (cl.equals(Object.class))
	    return null;
	else if (cl.isArray())
	    return xmlSchemaType(cl.getComponentType());
	else if (cl.equals(String.class))
	    return getXSD("string");
	else if (cl.equals(Integer.class))
	    return getXSD("int");
	else if (cl.equals(Integer.TYPE))
	    return getXSD("int");
	else if (cl.equals(Long.class))
	    return getXSD("long");
	else if (cl.equals(Long.TYPE))
	    return getXSD("long");
	else if (cl.equals(Boolean.class))
	    return getXSD("boolean");
	else if (cl.equals(Boolean.TYPE))
	    return getXSD("boolean");
	else if (cl.equals(Float.class))
	    return getXSD("float");
	else if (cl.equals(Float.TYPE))
	    return getXSD("float");
	else if (cl.equals(Double.class))
	    return getXSD("double");
	else if (cl.equals(Double.TYPE))
	    return getXSD("double");
	else if (cl.equals(java.util.Date.class))
	    return getXSD("timeInstant");
	else if (java.util.Collection.class.isAssignableFrom(cl))
	    return null;
	else
	    return getClass(cl);
    }

    /**
     * return the DAML Class associated with this Java Class
     */
    com.hp.hpl.mesa.rdf.jena.model.Resource getClass(Class cl)
	throws Exception
    {
	return model.createResource(ontologyURI + "#" + cl.getName());
    }

    /**
     * return a unique ID associated with this Java object.
     * If null, an anonymous resource will be used.
     * Callers may specialize this as required.
     */
    String getIndividualID(Object object)
    {
	return null;
    }

    /**
     * return the Resource associated with this Java object.
     * Callers may specialize this as required.
     */
    com.hp.hpl.mesa.rdf.jena.model.Resource getIndividual(Object object)
	throws Exception
    {
	String id = getIndividualID(object);
	if (id == null)
	    return getAnonymous();
	else
	    return model.createResource("#" + id);
    }

    /**
     * return an anonymous Resource
     */
    com.hp.hpl.mesa.rdf.jena.model.Resource getAnonymous()
	throws Exception
    {
	return model.createResource();
    }

    /**
     * return the Property associated with this Field
     */
    com.hp.hpl.mesa.rdf.jena.model.Property getProperty(java.lang.reflect.Field field)
	throws Exception
    {
	return model.createProperty(ontologyURI + "#" + field.getName());
    }

    /**
     * return the Resource associated with this XML Schema datatype
     */
    com.hp.hpl.mesa.rdf.jena.model.Resource getXSD(String datatype)
	throws Exception
    {
	return model.createResource(xmlURI + datatype);
    }

    public void add(Class cl)
	throws Exception
    {
	// Ontology
	if (firstClass)
	    {
		com.hp.hpl.mesa.rdf.jena.model.Resource ontology = model.createResource("");
		model.add(ontology,
			  com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
			  DAML.Ontology);
		model.add(ontology,
			  com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.comment,
			  "generated by java2daml (http://www.daml.org/2001/09/java2daml/)");
		firstClass = false;
	    }

	com.hp.hpl.mesa.rdf.jena.model.Resource damlClass = getClass(cl);
	model.add(damlClass,
		  com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
		  com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.Class);
	Class superclass = cl.getSuperclass();
	if (superclass != null)
	    {
		model.add(damlClass,
			  com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.subClassOf,
			  getClass(superclass));
	    }
	java.lang.reflect.Field fields[] = cl.getDeclaredFields();
	int i;
	for (i = 0; i < fields.length; i++)
	    {
		java.lang.reflect.Field field = fields[i];
		int modifiers = field.getModifiers();
		// XXX - filter
		com.hp.hpl.mesa.rdf.jena.model.Property property = getProperty(field);
		Class fieldType = field.getType();
		boolean scalar = ! ((fieldType.isArray())
				    || (java.util.Collection.class.isAssignableFrom(fieldType)));
		Object xsdType = xmlSchemaType(fieldType);
		if (xsdType == null)
		    {
			properties.properties.add(property.toString());
		    }
		else
		    {
			com.hp.hpl.mesa.rdf.jena.model.Resource toClassRestriction = getAnonymous();
			model.add(damlClass,
				  com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.subClassOf,
				  toClassRestriction);
			model.add(toClassRestriction,
				  com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
				  DAML.Restriction);
			model.add(toClassRestriction,
				  DAML.onProperty,
				  property);
			if (xsdType instanceof com.hp.hpl.mesa.rdf.jena.model.Resource)
			    model.add(toClassRestriction,
				      DAML.toClass,
				      (com.hp.hpl.mesa.rdf.jena.model.Resource) xsdType);
			else
			    model.add(toClassRestriction,
				      DAML.toClass,
				      xsdType); // XXX

			if (xsdType.toString().startsWith(xmlURI)) // XXX - better way?
			    properties.datatypeProperties.add(property.toString());
			else
			    properties.objectProperties.add(property.toString());
		    }

		// cardinality restriction(s)
		if (scalar)
		    {
			com.hp.hpl.mesa.rdf.jena.model.Resource cardinalityRestriction = getAnonymous();
			model.add(damlClass,
				  com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.subClassOf,
				  cardinalityRestriction);
			model.add(cardinalityRestriction,
				  com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
				  DAML.Restriction);
			model.add(cardinalityRestriction,
				  DAML.onProperty,
				  property);
			model.add(cardinalityRestriction,
				  DAML.cardinality,
				  "1");
		    }
	    }
	java.lang.Class classes[] = cl.getDeclaredClasses();
	for (i = 0; i < classes.length; i++)
	    add(classes[i]);
    }

    static java.text.DateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // XXX -- timezone?

    void addProperty(com.hp.hpl.mesa.rdf.jena.model.Resource resource,
		     com.hp.hpl.mesa.rdf.jena.model.Property property,
		     Object value)
	throws Exception
    {
	if (value == null)
	    return;
	else if ((value instanceof java.util.Collection))
	    {
		// XXX - use daml:collection instead?
		java.util.Vector vector = (java.util.Vector) value;
		java.util.Iterator iterator = vector.iterator();
		while (iterator.hasNext())
		    {
			addProperty(resource, property, iterator.next());
		    }
	    }
	else if ((value.getClass().isArray()))
	    {
		// XXX -- use daml:collection instead?
		int length = java.lang.reflect.Array.getLength(value);
		for (int i = 0; i < length; i++)
		    {
			addProperty(resource, property, java.lang.reflect.Array.get(value, i));
		    }
	    }
	else if (value instanceof java.util.Date)
	    {
		addProperty(resource, property, df.format(value));
	    }
	else
	    {
		// XXX -- better implementation?
		java.lang.Class type = value.getClass();
		if (type.equals(java.lang.Boolean.class)
		    || type.equals(java.lang.Double.class)
		    || type.equals(java.lang.Float.class)
		    || type.equals(java.lang.Integer.class)
		    || type.equals(java.lang.Long.class)
		    || type.equals(java.lang.Short.class)
		    || type.equals(java.lang.String.class))
		    model.add(resource, property, value);
		else
		    model.add(resource, property, add(value));
	    }
    }

    public com.hp.hpl.mesa.rdf.jena.model.Resource add(Object object)
	throws Exception
    {
	com.hp.hpl.mesa.rdf.jena.model.Resource resource;

	// has this object already been added?
	if ((resource = (com.hp.hpl.mesa.rdf.jena.model.Resource) objects.get(object)) != null)
	    return resource;

	Class cl = object.getClass();
	resource = getIndividual(object);
	model.add(resource,
		  com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
		  getClass(cl));

	// remember this object (here to handle cycles)
	objects.put(object,
		    resource);

	java.lang.reflect.Field fields[] = cl.getDeclaredFields();
	for (int i = 0; i < fields.length; i++)
	    {
		java.lang.reflect.Field field = fields[i];
		System.out.println(field); // XXX
		field.setAccessible(true);
		int modifiers = field.getModifiers();
		// XXX - filter
		com.hp.hpl.mesa.rdf.jena.model.Property property = model.createProperty(ontologyURI + "#" + field.getName());
		Object value = field.get(object);
		addProperty(resource, property, value);
	    }

	return resource;
    }

    public void write(String path)
	throws Exception
    {
	java.io.FileWriter writer = new java.io.FileWriter(path);
	write(writer);
    }

    public void write(java.io.Writer writer)
	throws Exception
    {
	if (! addedProperties)
	    {
		properties.addProperties(model);
		addedProperties = true;
	    }
	com.hp.hpl.mesa.rdf.jena.model.RDFWriter rdfWriter = model.getWriter();
	rdfWriter.setNsPrefix("daml", "http://www.daml.org/2001/03/daml+oil#"); // XXX - DAML.URI
	rdfWriter.write(model, writer, "");
	// model.write(writer);
    }

    public void setOntologyURI(String uri)
    {
	this.ontologyURI = uri;
    }
}
