
import com.hp.hpl.jena.ontology.*;
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.rdql.*;

import org.apache.cocoon.transformation.AbstractTransformer;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.ProcessingException;
import org.apache.avalon.framework.parameters.Parameters;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;

import java.util.*;
import java.io.IOException;

/**
 *
 * Class RDQLTransformer
 *
 * This transformer expects an XML document including elements of interest in
 * the following structure:
 *
 * <rdql:RDQL xmlns:rdql = "http://maria_sharapova.html">
 *  <rdql:modell>
 *      <rdql:ontology>myOntology</rdql:ontology>
 *      <rdql:instances>myInstances</rdql:instances>
 *  </rdql:modell>
 *  <rdql:query>
 *     <rdql:select>
 *          <rdql:param>myParam_1</rdql:param>
 *          .......
 *          <rdql:param>myParam_n</rdql:param>
 *      </rdql:select>
 *      <rdql:where>
 *          <rdql:triple>
 *              <rdql:subject>a subject</rdql:subject>
 *              <rdql:predicate>a predicate</rdql:predicate>
 *              <rdql:object>an object</rdql:object>
 *          </rdql:triple>
 *           ........
 *          <rdql:triple>
 *              <rdql:subject>another subject</rdql:subject>
 *              <rdql:predicate>another predicate</rdql:predicate>
 *              <rdql:object>another object</rdql:object>
 *          </rdql:triple>
 *      </rdql:where>
 *      <rdql:and>
 *          <rdql:operand1>my first operand</rdql:operand1>
 *          <rdql:operator>my operator</rdql:operator>
 *          <rdql:operand2>my second operand</rdql:operand2>
 *      </rdql:and>
 *       ......
 *      <rdql:and>
 *          <rdql:operand1>my first operand</rdql:operand1>
 *          <rdql:operator>my operator</rdql:operator>
 *          <rdql:operand2>my second operand</rdql:operand2>
 *      </rdql:and>
 *      <rdql:using/>
 *  </rdql:query>
 * </rdql:RDQL>
 *
 *
 * all other elements will be returned unmodified
 *
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: </p>
 *
 * @author Can Önder
 * @author Halgurt Mustafa Ali
 *
 * @version 1.0
 *
 */


public class RDQLTransformer extends AbstractTransformer {

    //ClassPath Property setzen System.getProperties().put...
    private String namespace;
    int count;

    boolean readOperand1;
    boolean readOperand2;
    boolean readOperator;
    boolean start;
    boolean readModel;
    boolean readQuery;
    boolean readOntology;
    boolean readInstances;
    boolean readSelect;
    boolean readParam;
    boolean readWhere;
    boolean readTriple;
    boolean readSubject;
    boolean readPredicate;
    boolean readObject;
    boolean readAnd;
    boolean readUsing;

    StringBuffer query;
    StringBuffer ontology;
    StringBuffer instances;
    StringBuffer param;
    StringBuffer subject;
    StringBuffer predicate;
    StringBuffer object;
    StringBuffer and;
    StringBuffer using;
    StringBuffer operand1;
    StringBuffer operand2;
    StringBuffer operator;

    Vector params;
    Vector triples;
    Vector ands;


    /**
     * Constructor
     */
    public RDQLTransformer () {

        namespace = "http://maria_sharapova.html";

        start         = false;
        readModel     = false;
        readQuery     = false;
        readOntology  = false;
        readInstances = false;
        readSelect    = false;
        readParam     = false;
        readWhere     = false;
        readTriple    = false;
        readSubject   = false;
        readPredicate = false;
        readObject    = false;
        readAnd       = false;
        readUsing     = false;
        readOperand1  = false;
        readOperand2  = false;
        readOperator  = false;

        query     = new StringBuffer ();
        ontology  = new StringBuffer ();
        instances = new StringBuffer ();
        param     = new StringBuffer ();
        subject   = new StringBuffer ();
        predicate = new StringBuffer ();
        object    = new StringBuffer ();
        and       = new StringBuffer ();
        using     = new StringBuffer ();
        operand1  = new StringBuffer ();
        operand2  = new StringBuffer ();
        operator  = new StringBuffer ();

        params  = new Vector ();
        triples = new Vector ();
        ands    = new Vector ();

        System.out.println ("Starting...");
    }


    /**
     *
     * no need to be implemented for the RDQLTramsformer;
     * inherited from org.apache.cocoon.transformation.AbstractTransformer
     *
     * @param resolver SourceResolver
     * @param objectModel Map
     * @param src String
     * @param par Parameters
     * @throws ProcessingException
     * @throws SAXException
     * @throws IOException
     */
    public void setup (SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException {}



    /**
     *
     * Parses the incoming SAX events.
     * The RDQLTransformer reacts on tags with the namespace (<xmlns:rdql= "http://maria_sharapova.html">)
     *
     * @param namespaceURI The namespace
     * @param localName The name of the current tag
     * @param qName The qualified name
     * @param attributes The attributes of the current tag
     * @throws SAXException
     */
    public void startElement (String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException {

        //react on the following tags of interest...
        if (namespaceURI.equals (namespace) && localName.equals ("RDQL")) {
            start = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("modell") && start) {
            readModel = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("query") && readModel) {
            readQuery = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("ontology") && readModel) {
            readOntology = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("instances") && readModel) {
            readInstances = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("select") && readQuery) {
            readSelect = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("param") && readSelect) {
            readParam = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("where") && readQuery) {
            readWhere = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("triple") && readWhere) {
            readTriple = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("subject") && readTriple) {
            readSubject = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("predicate") && readTriple) {
            readPredicate = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("object") && readTriple) {
            readObject = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("and") && readQuery) {
            readAnd = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("using") && readQuery) {
            readUsing = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("operand1") && readAnd) {
            readOperand1 = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("operand2") && readAnd) {
            readOperand2 = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("operator") && readAnd) {
            readOperator = true;
        }

        //do not modify any other tags...
        else {
            super.startElement (namespaceURI, localName, qName, attributes);
        }

    }





    /**
     *
     * Reads the content between the tags of interest.
     * The content is saved in StringBuffers
     *
     * @param ch The whole XML document as an character array
     * @param start The start index of the tag content
     * @param length The length of the content between the start and the end tag
     *
     */
    public void characters (char[] ch, int start, int length) {

        if (readOntology) {
            ontology.append (ch, start, length);
            readOntology = false;
        }

        if (readInstances) {
            instances.append (ch, start, length);
            readInstances = false;
        }

        if (readSubject) {
            subject.append (ch, start, length);
            readSubject = false;
        }

        if (readPredicate) {
            predicate.append (ch, start, length);
            readPredicate = false;
        }

        if (readObject) {
            object.append (ch, start, length);
            readObject = false;
        }

        if (readUsing && (length != 0)) {
            using.append (ch, start, length);
            readUsing = false;
        }

        if (readParam) {
            param.append (ch, start, length);
        }

        if (readOperand1 && (length != 0)) {
            operand1.append (ch, start, length);
            readOperand1 = false;
        }

        if (readOperand2 && length != 0) {
            operand2.append (ch, start, length);
            readOperand2 = false;
        }

        if (readOperator && (length != 0)) {
            operator.append (ch, start, length);
            readOperator = false;
        }
    }






    /**
     *
     * After any triple declaration, param declaration or and declaration, we compose the specified part
     * of the query (as there can be multiple triple, param or and declarations) before generating and
     * executing the query itself
     *
     * @param namespaceURI The namespace
     * @param localName The name of the current tag
     * @param qName The qualified name
     * @throws SAXException
     *
     */
    public void endElement (String namespaceURI, String localName, String qName) throws SAXException {

        //add this triple statement to the vector of all triples, belonging to this query
        if (namespaceURI.equals (namespace) && localName.equals ("triple")) {

            readTriple = false;
            triples.addElement (generateTriple (subject.toString (), predicate.toString (), object.toString ()));
            subject   = new StringBuffer ();
            predicate = new StringBuffer ();
            object    = new StringBuffer ();
        }

        //add this param statement to the vector of all params, belonging to this query
        else if (namespaceURI.equals (namespace) && localName.equals ("param")) {

            readParam = false;
            params.addElement (param.toString ());
            param = new StringBuffer ();
        }

        //add this and statement to the vector of all and-statements, belonging to this query
        else if (namespaceURI.equals (namespace) && localName.equals ("and")) {

            readAnd = false;
            if (operand1.toString ().trim ().length() != 0 && operand2.toString ().trim ().length() != 0 && operator.toString ().trim ().length() != 0) {
                ands.addElement (generateAnd (operand1.toString ().trim (), operand2.toString ().trim (), operator.toString ().trim ()));
            }
            operand1 = new StringBuffer ();
            operand2 = new StringBuffer ();
            operator = new StringBuffer ();
        }

        //when we arrive at the end of the query tag, we can compose and execute the RDQL query, considering all the relevant information
        else if (namespaceURI.equals (namespace) && localName.equals ("query")) {

            //first, compose the query from "select", "where", "and" and "using" tags
            readQuery = false;
            query.append ("Select ");

            for (int i = 0; i < params.size (); i++) {
                query.append ("?" + (String)params.elementAt (i) + " ");
            }

            query.append ("where ");

            for (int i = 0; i < triples.size (); i++) {
                query.append ((String)triples.elementAt (i));
            }

            for (int i = 0; i < ands.size(); i++) {
                query.append (" and " + ands.elementAt(i));
            }

            if (using.toString ().trim ().length () != 0) {
                String usingTrimmed = using.toString ().trim ();
                query.append (" using " + usingTrimmed);
            }

            //query is composed, now execute and embed the result in a "result" tag...
            super.startElement ("", "result", "result", null);
            OntModel m = null;
            Query q    = null;

            //constructs an empty model of the type RDFS
            m = ModelFactory.createOntologyModel (OntModelSpec.RDFS_MEM, null);

            //add the ontology and the instances to the model
            m.getDocumentManager ().addAltEntry (ontology.toString (), instances.toString ());
            //Iread the instances...
            m.read (instances.toString ());
            //...and execute the query...
            try {
                q = new Query (query.toString ());
            }

            catch (Exception e) {
                System.out.println ("An Error occured during query instantiation ");
                e.printStackTrace ();
                reset ();
            }

            System.out.println ("Query: " + query.toString ());
            q.setSource (m);

            QueryExecution qe = new QueryEngine (q);
            QueryResults qr = qe.exec ();

            //go through the resultset until it is empty
            for (Iterator iter = qr; qr.hasNext (); ) {

                ResultBinding rbind = (ResultBinding)iter.next ();

                for (int i = 0; i < params.size (); i++) {

                    Object obj = rbind.getValue ((String)params.elementAt (i)).getRDFLiteral ().getValue ();
                    super.startElement ("", params.elementAt (i).toString (), params.elementAt (i).toString (), null);
                    super.characters (obj.toString ().toCharArray (), 0, obj.toString ().length ());
                    super.endElement ("", params.elementAt (i).toString (), params.elementAt (i).toString ());
                }
            }

            //now, we have the result
            super.endElement ("", "result", "result");

            //query is executed, now reset query parameters...
            params.removeAllElements ();
            triples.removeAllElements ();
            ands.removeAllElements ();

            query = new StringBuffer ();
            and   = new StringBuffer ();
            using = new StringBuffer ();
        }


        //recycle all variables when we arrive at the end...
        else if (namespaceURI.equals (namespace) && localName.equals ("RDQL")) {
            reset();
        }

        //do nothing when these elements occur...
        else if (namespaceURI.equals (namespace) && (localName.equals ("instances") ||
                                                     localName.equals ("modell")    ||
                                                     localName.equals ("select")    ||
                                                     localName.equals ("subject")   ||
                                                     localName.equals ("predicate") ||
                                                     localName.equals ("object")    ||
                                                     localName.equals ("where")     ||
                                                     localName.equals ("and")       ||
                                                     localName.equals ("using")     ||
                                                     localName.equals ("ontology")  ||
                                                     localName.equals ("operand1")  ||
                                                     localName.equals ("operand2")  ||
                                                     localName.equals ("operator")))  {
        }


        //do not modify any other elements
        else {
            super.endElement (namespaceURI, localName, qName);
        }
    }




    /**
     *
     * generates a single RDQL triple in the correct RDQL syntax, putting subject, predicate
     * and object together
     *
     * @param subject The subject
     * @param predicate The predicate
     * @param object The object
     * @return The triple
     *
     */
    public String generateTriple (String subject, String predicate, String object) {

        StringBuffer retval = new StringBuffer ();
        retval.append ("(");

        if (!subject.startsWith ("\"")) {
            retval.append ("?");
        }

        retval.append (subject + ", <" + predicate + ">, ");

        if (!object.startsWith ("\"")) {
            retval.append ("?");
        }

        retval.append (object + ")");

        System.out.println ("Triple: " + retval.toString ());

        return retval.toString ();
    }




    /**
     *
     * generates a single RDQL and statement in the correct RDQL syntax, putting all operands and
     * the operator together
     *
     * @param op1 First operand
     * @param op2 Second operand
     * @param operator The operator
     * @return String
     *
     */
    public String generateAnd (String op1, String op2, String operator) {

        StringBuffer retval = new StringBuffer ();

        if (!op1.startsWith ("\"")) {
            retval.append ("?");
        }

        retval.append (op1 + " " + operator + " ");

        if (!op2.startsWith ("\"")) {
            retval.append ("?");
        }

        retval.append (op2);
        return retval.toString ();
    }



    /**
     * Recycles all the variables and parameters
     */
    private void reset () {

        //boolean variables
        start = false;
        readModel = false;
        readQuery = false;
        readOntology = false;
        readInstances = false;
        readSelect = false;
        readParam = false;
        readWhere = false;
        readTriple = false;
        readSubject = false;
        readPredicate = false;
        readObject = false;
        readAnd = false;
        readUsing = false;
        readOperand1 = false;
        readOperand2 = false;
        readOperator = false;

        //StringBuffers
        ontology = new StringBuffer ();
        instances = new StringBuffer ();
        query = new StringBuffer ();
        param = new StringBuffer ();
        subject = new StringBuffer ();
        predicate = new StringBuffer ();
        object = new StringBuffer ();
        and = new StringBuffer ();
        using = new StringBuffer ();
        operand1 = new StringBuffer ();
        operand2 = new StringBuffer ();
        operator = new StringBuffer ();

        //Vectors
        params.removeAllElements ();
        triples.removeAllElements ();
        ands.removeAllElements ();
    }
}
