/* This software was developed within the context of the project Metaphor for Science
   Museums (Mesmuses) in the framework of the Information Society Technology
   Programme, part of the Fifth Research And Technological Development
   Framework Programme of the European Community, under the Consortium
   Agreement dated <June 30, 2001>, between INRIA, Finsiel Spa, IMSS,
   Valoris , CSI, ICS-FORTH, ENSTB, EDW International.
*/

// RDF_Validator.java

/*
 *  CHANGE LOG:
 *  Each entry starts with a date, who did the changed and what has been done.
 *  Separated by ':'.
 *
 *  30.05.2002 : Karsten Tolle : Changed to enable full XML datatype support. The
 *                               following methodes have been changed:
 *                               type_check;
 *  10.09.2002 : Karsten Tolle : Changed the underlying model and therefore changed
 *                               also the validator. The parts of type check and range_domain
 *                               check have been moved mainly to the objects and the check is
 *                               just called from here. The other checks are based on the dag
 *                               structure and stay in here.
 *  12.11.2002 : Karsten Tolle : Changed the RDF_DAG structure and therefore
 *                               adopted the validataor.
 */


package gr.forth.ics.vrp.corevrp.validator;


import java.util.*;
import java.lang.Exception;
import gr.forth.ics.vrp.corevrp.*;
import gr.forth.ics.vrp.corevrp.model.*;
import gr.forth.ics.vrp.corevrp.vocabulary.*;

/**
 * RDF_Validator.java
 *
 * @author Karsten Tolle & Sofia Alexaki
 * @version 2.5  (30 Januar 2003)
 */
public class RDF_Validator{

	/** Create a new RDF_Validator.*/
	public RDF_Validator(Model mdl) {
		 basis_model = mdl;
	}

	/** The model we want to validate.*/
	public Model basis_model;
	/** Representing the correlations of classes. Should be free of loops. */
	private RDF_DAG class_dag;
	/** Representing the correlations of Properties. Should be free of loops. */
	private RDF_DAG property_dag ;
	private RDF_Error e = new RDF_Error();
	private boolean verbose = false;
	private boolean graph = false;
	private boolean classLoop = false;
	private boolean propertyLoop = false;
	private boolean inheritance = false;
	private boolean subsetSubpropertyDomainRange = false;
	private boolean uniqueDefinedDomainRange = false;
  	private boolean typing = false;
  	private boolean sourceTargetTypes = false;
//   private boolean uniqueDomain = false;
   private Output out = new Output();

	/** Time required for domain and range inheritance to the subProperties */
	private long inheritTime = 0;

		// setting methodes for the options
		/** Setting the verbose option. Default is 'false'. */
		public void setverbose(boolean b) { this.verbose = b; }
     	/**Setting the graph option. Default is 'false'. */
     	public void setgraph(boolean b) { this.graph = b; }
     	/** Setting the classLoop option. Default is 'false'.*/
     	public void setclassLoop(boolean b) { this.classLoop = b; }
     	/** Setting the propertyLoop option. Default is 'false'.*/
     	public void setpropertyLoop(boolean b) { this.propertyLoop = b; }
     	/** Setting the inheritance option. Default is 'false'.*/
     	public void setinheritance(boolean b) { this.inheritance = b; }
     	/**Setting the subsetSubpropertyDomainRange option. Default is 'false'. */
     	public void setsubsetSubpropertyDomainRange(boolean b) { this.subsetSubpropertyDomainRange = b; }
     	/**Setting the uniqueDefinedDomainRange option. Default is 'false'. */
     	public void setuniqueDefinedDomainRange(boolean b) { this.uniqueDefinedDomainRange = b; }
     	/** Setting the typing option. Default is 'false'.*/
     	public void settyping(boolean b) { this.typing = b; }
     	/**Setting the sourceTargetTypes option. Default is 'false'. */
     	public void setsourceTargetTypes(boolean b) { this.sourceTargetTypes = b; }
//    	public void setuniqueDomain(boolean b) { this.uniqueDomain = b; }


	/**
	 * Returns the subClasses Directed Acyclic Graph.
	 */
	public RDF_DAG getsubClassOfDAG() {
		return  class_dag;
	}

	/**
	 * Sets the subClasses Directed Acyclic Graph.
	 */
	public void setsubClassOfDAG(RDF_DAG class_dag ) {
		 this.class_dag = class_dag;
	}

	/**
	 * Returns the subProperties Directed Acyclic Graph.
	 */
	public RDF_DAG getsubPropertyOfDAG() {
		return  property_dag;
	}

	/**
	 * Sets the subClasses Directed Acyclic Graph.
	 */
	public void setsubPropertyOfDAG(RDF_DAG property_dag ) {
		 this.property_dag = property_dag;
	}

  /**
	* Returns the time required to inherit the domain and the range to the
	* sub properties
	*/
  public long getinheritTime(){
	return inheritTime;
  }

	/**
	 * The method starting all the jobs for the validation.
   * Returns true if the total descriptions are valid
	 */
	public boolean validate() {
		//System.out.println("start validation");
	// contact the namespaces we need
    boolean valid = false;
		// check for loops in subPropertyOf and check if the domain and range of
		// the sub property fit to the domain and range of the super property
		// we can follow the topological order to do so
		//new since 8 April 2001
		//Now It is possible to choose validation constraints
		// domain and range check
    RDF_Error e = new RDF_Error();
    String by = "RDF_Validator.validate";
    if (subsetSubpropertyDomainRange && !propertyLoop) {
       e.emit_warning("The constraint 'Property Hierarchy Loops' will also be checked. Verification of the constraint 'Domain/Range of SubProperties' can be done as long as there is no loop in the subproperty hierarchy", by);
       propertyLoop = true;
    }
    if (inheritance && !propertyLoop) {
        e.emit_warning("The constraint 'Property Hierarchy Loops' will also be checked. Inference on Domain/Range can be done as long as there is no loop in the subproperty hierarchy", by);
        propertyLoop = true;
    }
    boolean no_loop = false;
		if (propertyLoop)
        no_loop = subProperty_check();
     if (inheritance) {
         if (no_loop )  {
            long start_time = System.currentTimeMillis();
            inheritDomainRange();
            long end_time = System.currentTimeMillis();
            inheritTime = end_time-start_time;
         } else
            e.emit_warning("Inheritance of domain/range cannot performed since "
            + "there are loops " + "in the property hierarchy", by);
     }
     if (subsetSubpropertyDomainRange) {
       if (no_loop )
         subpropertyDomainRange_check();
       else
         e.emit_warning("Domain/Range of subProperties is not checked  since "
          + "there are loops " + "in the property hierarchy", by);
     }

     if (sourceTargetTypes) domain_range_check();
		// all Statements valid and type check?, type_check will call "subClassOf_check()"
  	   if (typing)
         type_check();
		if (classLoop)
		     subClassOf_check();
      //This semantic constraint is set only from RSSDB
//	   if (uniqueDomain) uniqueDomain();
      if (verbose) out.outln("finished validation", false);
    if (Main.error_counter == 0)
      valid = true;
		return valid;
  }

   /**
    * Checks if the range is unique
    */
   private boolean  isRangeUnique(RDF_Property p) {
       boolean b = false;
       if ( p.getrange().size() == 1) {
            b = true;
       }
       return b;
	 }

   /**
    * Checks if there multi range declarations
    */
    private boolean isRangeMultiDefined(RDF_Property p) {
       boolean b = false ;
       if ( p.getrange().size() > 1) {
         e.emit_error(3, 30, p.getID(), "RDF_Validator.isRangeMultiDefined");
         b = true;
       }
       return b;
	 }

	/**
	 * Testing if each element of the classes RDF_Class, RDF_Property,
	 * RDF_Statement and RDF_Container have the corresponding type.
	 */
	private boolean type_check() {
  	  if (verbose) System.out.println("Checking the types and statements ...");
	  boolean valid = true;

	Collection valset = basis_model.getNodes();
        for (Iterator it = valset.iterator(); it.hasNext(); ) {
          Object object = it.next();
          if (object instanceof RDF_Statement) {
            RDF_Statement rdf_statement = (RDF_Statement)object;
            if (! rdf_statement.valid()) {
              e.emit_error(3, 34, rdf_statement.getID(), "RDF_Validator.type_check");
              valid = false;
            }
				if (! rdf_statement.has_type((RDF_Class)this.basis_model.existing.get(rdf.Statement))) {
              e.emit_error(3, 35, rdf_statement.getID(), "RDF_Validator.type_check");
              valid = false;
            }
          }
          if (object instanceof RDF_Class) {
            RDF_Class rdf_class = (RDF_Class)object;
            // We are not testing if a RDF or RDF Schema Class has type class.
            if (!(rdf.Classes.contains(rdf_class.getID()) ||
              rdfschema.Classes.contains(rdf_class.getID()) || rdf_class.getID().startsWith(XMLSchema_DT.NS))) {
                if (! rdf_class.has_type((RDF_Class)this.basis_model.existing.get(rdfschema.Class))) {
                  e.emit_error(3, 36, rdf_class.getID(), "RDF_Validator.type_check");
                  valid = false;
                }
            }
          }
          if (object instanceof RDF_Property) {
            RDF_Property rdf_property = (RDF_Property)object;
            // We are not testing if a RDF or RDF Schema Property has the type property.
            if (!(rdf.Properties.contains(rdf_property.getID()) ||
            rdfschema.Properties.contains(rdf_property.getID()) )) {
              if (rdf_property.getID().startsWith(rdf.msp)) {
                String s = rdf_property.getID();
                int k = rdf.msp.length();
                s = s.substring(k);
                try {
						Integer I = new Integer(s);
                } catch (Exception ex) {
                  System.out.println(ex);
                  e.emit_error(3, 37, rdf_property.getID(), "RDF_Validator.type_check");
                } // end try
              } else {
                if (! rdf_property.has_type((RDF_Class)this.basis_model.existing.get(rdf.Property))) {
                  e.emit_error(3, 37, rdf_property.getID(), "RDF_Validator.type_check");
                  valid = false;
                }
              } // end else
            }

            // do we need this here, moved to domain & range check
            /*if ( isRangeMultiDefined(rdf_property) )
            ;
            else  if ( isRangeUnique(rdf_property) ) {
              ArrayList links = rdf_property.getlink();
              for (int i=0; i<links.size(); ) {
                Link link = (Link)links.get(i++);
                Object obj = link.getobject();
                resourceLiteralTypeCheck(rdf_property.getID(), obj, rdf_property.getrange());
              } // end for
            } // end else if
            */
          } // end 'if (object instanceof RDF_Property)
			 if (object instanceof RDF_Container) {
            RDF_Container rdf_container = (RDF_Container)object;
				if (! rdf_container.has_type((RDF_Class)this.basis_model.existing.get(rdf.Bag)) ||
				rdf_container.has_type((RDF_Class)this.basis_model.existing.get(rdf.Seq)) ||
            rdf_container.has_type((RDF_Class)this.basis_model.existing.get(rdf.Alt))) {
              e.emit_error(3, 38, rdf_container.getID(), "RDF_Validator.type_check");
              valid = false;
          }
        }
      }
    return valid;
  } // end method



	/**
	 * Tests for loops in the subPropertyOf Statements
	 */
	private boolean subProperty_check() {
		if (verbose) out.outln("Checking for loops in properties ...", false);
		// testing for loops and
		// create the topological order
   	create_property_dag();
		boolean no_loop = property_dag.loopcheck();
		if (! no_loop) { // loop found
			for (Iterator i = property_dag.getloop_element().iterator(); i.hasNext(); ) {
				Object o = i.next();
										  String s;
										  if (o instanceof Resource) s = ((Resource)o).getID();
										  else s = o.toString();
				e.emit_error(3, 29, s, "Validator.subProperty_check");
			}
		}
		if (verbose && no_loop) out.outln("no loops found in Property definitions", false);
		if (verbose && no_loop) {
			out.outln("The topological order of subPropertyOf (top down): ", true);
			out.outln(property_dag.output(), true);
		}
		return no_loop;
	}

	/**
	 * Testing for loops in the subClassOf Statements.
	 */
	public boolean subClassOf_check() {
		if (verbose) out.outln("Checking for loops in classes ...", false);
		create_class_dag();
		boolean no_loop = class_dag.loopcheck();
		if (! no_loop) { // loop found
			for (Iterator i = class_dag.getloop_element().iterator(); i.hasNext(); ) {
				Object o = i.next();
										  String s;
										  if (o instanceof Resource) s = ((Resource)o).getID();
										  else s = o.toString();
				e.emit_error(3, 29, s, "Validator.subClassOf_check");
			}
		}
		if (verbose && no_loop) out.outln("no loops found in Class definitions", false);
		if (verbose) {
			out.out("The topological order of subClassOf (top down): ", true);
			out.outln(class_dag.output(), true);
		}
		//basis_model.subClassOf.loopcheck();
		return no_loop;
	}

	/**
	 * Inherits the domain and range of a property to its subProperty
	 * in case they are not already defined.
	 */
   private void inheritDomainRange() {
      ArrayList list = property_dag.getorder();
      //new since 8 April 2001 just add one line
	   for (int i = 0; i < list.size(); ) {
              // this way fist comes the super property
				  // and then the sub property
              boolean inhR = true;
              boolean inhD = true;
              Object sub_obj = list.get(i++);
              RDF_Property rdf_property;
              if (sub_obj instanceof Resource) { // it also should be a RDF_Property
                  rdf_property = (RDF_Property)sub_obj;
              } else {
                  rdf_property = (RDF_Property)basis_model.getResource(sub_obj.toString());
              }
              if (rdf_property.getsubPropertyOf().size() > 0) {
                if (rdf_property.getsubPropertyOf().size() > 1 &&  rdf_property.getrange().isEmpty() ) {
						e.emit_warning("Because of property " + rdf_property.getID() +                             " of having multiple subproperties, inheritance of range is not performed.", "RDF_Validator.inheritDomainRange");
                  inhR = false;
                } //difficult case
                if (rdf_property.getsubPropertyOf().size() > 1 &&  rdf_property.getdomain().isEmpty() ) {
                  e.emit_warning("Because of property " + rdf_property.getID() +
                       " of having multiple subproperties, inheritance of domain is not performed.", "RDF_Validator.inheritDomainRange");
                  inhD = false;
                } //difficult case

                ArrayList list2 = rdf_property.getsubPropertyOf();
                for (int j=0; j<list2.size(); ) {
                  // getting the super property
                  RDF_Property super_P = (RDF_Property)list2.get(j++);
						if (!super_P.getrange().isEmpty() && inhR) {
                    if (rdf_property.getrange().isEmpty()) rdf_property.setrange(super_P.getrange());
                  }
                  if (super_P.getdomain().size() > 0 && inhD) { // we have to test or do something
                    if (rdf_property.getdomain().size() == 0)
                     rdf_property.setdomain(super_P.getdomain());
                  }
                 } // end for
                }
              } // end for
      } // end method

   /**
	 * Testing if the domain and range of a sub property fit
    * to the domain and range of the super properties.
    */
   public void subpropertyDomainRange_check() {
       ArrayList list2 = property_dag.getorder();
       // in the extrem case we might not have every
       // information we need here
       //System.out.println("The topological order of subPropertyOf: ");
       //new since 8 April 2001 just add one line
       for (int i = 0; i < list2.size(); ) {
              // this way fist comes the super property
              // and then the sub property
              Object sub_obj = list2.get(i++);
				  RDF_Property rdf_property;
              if (sub_obj instanceof Resource) {
                  rdf_property = (RDF_Property)sub_obj;
              } else {
                  rdf_property = (RDF_Property)basis_model.getResource(sub_obj.toString());
              }
              ArrayList list = rdf_property.getsubPropertyOf(); // a list of RDF_Properties
              for (int j=0; j<list.size(); ) {
                  // getting the super property
                  RDF_Property super_P = (RDF_Property)list.get(j++);
                  //System.out.println("sPO_range_domain; super_P.getrange= "+super_P.getrange()+" super_P.getdomain= "+super_P.getdomain());
                  // first checking the range
                  if (!super_P.getrange().isEmpty() ) {
                      RDF_Class range_super = (RDF_Class)super_P.getrange().get(0);
                      //Modified 2-09-2001
                      //in order to include the case where the range of the
                      // superproperty is defined while the range of the subproperty
                      //is not.
                      if (rdf_property.getrange().isEmpty())  {
                          e.emit_error(3, 39, rdf_property.getID()+" and the super property "+super_P.getID(), "RDF_Validator.sPO_range_domain");
                      } else {
                          RDF_Class range_sub = (RDF_Class)rdf_property.getrange().get(0);
                          if (! (range_sub == range_super)) {
                              if (! compare_classes(range_sub.getID(), range_super.getID()) && !( (range_sub.getID().equals(rdfschema.Literal) && range_super.getID().equals(XMLSchema_DT.stringDT) )  || (range_sub.getID().equals(XMLSchema_DT.stringDT) && range_super.getID().equals(rdfschema.Literal))) )  {
                                  e.emit_error(3, 39, rdf_property.getID()+" and the super property "+super_P.getID(), "RDF_Validator.sPO_range_domain");
                              }
								  }
                      } // end else
                  }
                  // then the domain
                  if (super_P.getdomain().size() > 0) { // we have to test or do something
                      if (rdf_property.getdomain().size() == 0 && !super_P.getdomain().contains((RDF_Class)basis_model.existing.get(rdfschema.Resource))){
                          e.emit_error(3, 40, rdf_property.getID()+" and the super property "+super_P.getID(), "RDF_Validator.sPO_range_domain");
        	      } else {  //each Class in domain_list of sub must be contained by
                          //domain_list of super_P
                          ArrayList sub_list = rdf_property.getdomain();
                          ArrayList super_list = super_P.getdomain();
                          for (int l=0; l<sub_list.size(); ) {
                              RDF_Class next = (RDF_Class)sub_list.get(l++);
                              boolean valid = false;
                              for (int k=0; k<super_list.size(); ) {
                                  RDF_Class super_next = (RDF_Class)super_list.get(k++);
                                  valid = compare_classes(next.getID(), super_next.getID());
                                  if (valid) break;
                              }
                              if (! valid) {
                                  e.emit_error(3, 40, rdf_property.getID()+" and the super property "+super_P.getID(), "RDF_Validator.sPO_range_domain");
                              }
                          }
                      }//end else
                  }
              }
			 }
      }


	/**
	 * The class with URI <sub> should be a subset of the class with URI <super>.
	 * The method is only called it the strings are not equal.
	 */
	private boolean compare_classes(String sub, String super_C) {
		boolean valid = false;
		if (sub.equals(super_C)) return true;
		RDF_Class sub_class = (RDF_Class)basis_model.getResource(sub);
		// we have to find the URI <super> in the subClassOf list
		// of the sub_class or in one of their superclasses.
		if (sub_class.containssubClassOf_type(super_C)) return true;
		else {
                        ArrayList list = sub_class.getsubClassOf();
			for (int i=0; i<list.size(); ) {
                                RDF_Class c = (RDF_Class)list.get(i++);
				valid = compare_classes(c.getID(), super_C);
			}
		}
		return valid;
	}

	/**
	 * The domain and range check.
	 */
	private void domain_range_check() {
  	  if (verbose) System.out.println("Checking for domain and range ...");
	  Collection valset = basis_model.getNodes();
	  for (Iterator it = valset.iterator(); it.hasNext(); ) {
	      Object next = it.next();
	      if (next instanceof RDF_Property) {
		RDF_Property rdf_property = (RDF_Property)next;
                try {
                    rdf_property.range_domain_check();
                } catch(Exception ex) {
                    RDF_Error e = new RDF_Error();
                    e.emit_error(3,41,""+ex.getMessage(), "validator.domain_range_check");
					 }
			}
			 }
	} // end domain_range_check

      public void create_class_dag() {
	 class_dag = new RDF_DAG();
	 for (Iterator it = basis_model.getClasses().iterator(); it.hasNext(); ) {
		RDF_Class c = (RDF_Class)it.next();
		ArrayList sco = c.getsubClassOf();
		class_dag.enter(c);
		for (Iterator it2 = sco.iterator(); it2.hasNext(); ) {
		  RDF_Class c2 = (RDF_Class)it2.next();
		  class_dag.enter(c, c2);
		}
	 }
	 //class_dag.enumerate();
	 class_dag.output();
      }

	public void create_property_dag() {
		//System.out.println("starting properties");
		property_dag = new RDF_DAG();
		for (Iterator it = basis_model.getProperties().iterator(); it.hasNext(); ) {
		  RDF_Property p = (RDF_Property)it.next();
		  ArrayList spo = p.getsubPropertyOf();
						property_dag.enter(p);
		  for (Iterator it2 = spo.iterator(); it2.hasNext(); ) {
			RDF_Property next = (RDF_Property)it2.next();
			property_dag.enter(p, next);
		  }
		}
  }
}
