public class Alignment<ONTORES1, ONTORES2> {

	// Les deux ontologies mise en jeu dans ce mapping
	private OntoContainer<ONTORES1> onto1 = null;
	private OntoContainer<ONTORES2> onto2 = null;

	// La liste ordonnée de tous les mappings
	private SortedSet<Mapping<ONTORES1, ONTORES2>> mapping = null;

	// Les index vers les sous-ensembles
	private SortedMap<URI, SortedSet<Mapping<ONTORES1, ONTORES2>>> index1 = null;
	private SortedMap<URI, SortedSet<Mapping<ONTORES1, ONTORES2>>> index2 = null;

	// Les index vers les concepts considérés comme mapping impossible
	private SortedMap<URI, NoMappingPossible<ONTORES1>> impossibleMapping1 = null;
	private SortedMap<URI, NoMappingPossible<ONTORES2>> impossibleMapping2 = null;

	/**
	 * @param onto1
	 * @param onto2
	 */
	public Alignment(OntoContainer<ONTORES1> onto1, OntoContainer<ONTORES2> onto2) {
		this.onto1 = onto1;
		this.onto2 = onto2;
		mapping = new TreeSet<Mapping<ONTORES1,ONTORES2>>();
		// Building the indexes
		// For onto1
		index1 = new TreeMap<URI, SortedSet<Mapping<ONTORES1,ONTORES2>>>();
		impossibleMapping1 = new TreeMap<URI, NoMappingPossible<ONTORES1>>();			
		// For onto2
		index2 = new TreeMap<URI, SortedSet<Mapping<ONTORES1,ONTORES2>>>();
		impossibleMapping2 = new TreeMap<URI, NoMappingPossible<ONTORES2>>();
	}

	public OntoContainer<ONTORES1> getOnto1() {
		return onto1;
	}

	public OntoContainer<ONTORES2> getOnto2() {
		return onto2;
	}

	/** Return an unmodifiable view of the index for ontology 1.
	 * @return
	 * @see Collections#unmodifiableSortedMap(SortedMap)
	 */
	public SortedMap<URI, SortedSet<Mapping<ONTORES1, ONTORES2>>> getIndex1() {
		return Collections.unmodifiableSortedMap(index1);
	}

	/** Return an unmodifiable view of the index for ontology 2.
	 * @return
	 * @see Collections#unmodifiableSortedMap(SortedMap)
	 */
	public SortedMap<URI, SortedSet<Mapping<ONTORES1, ONTORES2>>> getIndex2() {
		return Collections.unmodifiableSortedMap(index2);
	}

	/** Return an unmodifiable view of all mapping.
	 * @return
	 */
	public SortedSet<Mapping<ONTORES1, ONTORES2>> getMapping() {
		return Collections.unmodifiableSortedSet(mapping);
	}

	/** Return an unmodifiable view of not possible mapping from ontology 1.
	 * @return
	 */
	public SortedMap<URI, NoMappingPossible<ONTORES1>> getImpossibleMapping1() {
		return Collections.unmodifiableSortedMap(impossibleMapping1);
	}

	/** Return an unmodifiable view of not possible mapping from ontology 2.
	 * @return
	 */
	public SortedMap<URI, NoMappingPossible<ONTORES2>> getImpossibleMapping2() {
		return Collections.unmodifiableSortedMap(impossibleMapping2);
	}

	public synchronized void addMap(Mapping<ONTORES1,ONTORES2> map) {
		// Adding to first index
		if(index1.containsKey(onto1.getURI(map.getFirstConcept()))) {
			index1.get(onto1.getURI(map.getFirstConcept())).add(map);
		}
		else {
			TreeSet<Mapping<ONTORES1,ONTORES2>> index1map = new TreeSet<Mapping<ONTORES1,ONTORES2>>();
			index1map.add(map);
			index1.put(onto1.getURI(map.getFirstConcept()), index1map);
		}
		// Adding to second index
		if(index2.containsKey(onto2.getURI(map.getSecondConcept()))) {
			index2.get(onto2.getURI(map.getSecondConcept())).add(map);
		}
		else {
			TreeSet<Mapping<ONTORES1,ONTORES2>> index2map = new TreeSet<Mapping<ONTORES1,ONTORES2>>();
			index2map.add(map);
			index2.put(onto2.getURI(map.getSecondConcept()), index2map);
		}
		// Adding to all mapping list
		mapping.add(map);
	}

	private static <ONTORES1,ONTORES2> void removeMapFromIndex(
			URI cptUri,
			Mapping<ONTORES1,ONTORES2> map,
			SortedMap<URI, SortedSet<Mapping<ONTORES1, ONTORES2>>> index) {
		if(index.containsKey(cptUri)) {
			index.get(cptUri).remove(map);
			if(index.get(cptUri).isEmpty())
				index.remove(cptUri);
		}
	}

	public void removeMap(Mapping<ONTORES1,ONTORES2> map) {
		URI uri1 = onto1.getURI(map.getFirstConcept());
		removeMapFromIndex(uri1, map, index1);

		URI uri2 = onto2.getURI(map.getSecondConcept());
		removeMapFromIndex(uri2, map, index2);

		mapping.remove(map);
	}

	public void removeMapFromConcept1(ONTORES1 concept1) {
		URI concept1URI = onto1.getURI(concept1);
		if(index1.containsKey(concept1URI)) {
			for(Mapping<ONTORES1,ONTORES2> map : index1.get(concept1URI)) {
				URI concept2URI = onto2.getURI(map.getSecondConcept());
				removeMapFromIndex(concept2URI, map, index2);
				mapping.remove(map);
			}
			index1.remove(concept1URI);
		}
	}

	public void removeMapFromConcept2(ONTORES2 concept2) {
		URI concept2URI = onto2.getURI(concept2);
		if(index2.containsKey(concept2URI)) {
			for(Mapping<ONTORES1,ONTORES2> map : index2.get(concept2URI)) {
				URI concept1URI = onto1.getURI(map.getFirstConcept());
				removeMapFromIndex(concept1URI, map, index1);
				mapping.remove(map);
			}
			index2.remove(concept2URI);
		}
	}

	public SortedSet<Mapping<ONTORES1, ONTORES2>> getAllMappingFor1(ONTORES1 ontores) {
		if(index1.containsKey(onto1.getURI(ontores))) {
			return Collections.unmodifiableSortedSet(index1.get(onto1.getURI(ontores)));
		}
		else {
			return new TreeSet<Mapping<ONTORES1,ONTORES2>>();
		}
	}

	public SortedSet<Mapping<ONTORES1, ONTORES2>> getAllMappingFor2(ONTORES2 ontores) {
		if(index2.containsKey(onto2.getURI(ontores))) {
			return Collections.unmodifiableSortedSet(index2.get(onto2.getURI(ontores)));
		}
		else {
			return new TreeSet<Mapping<ONTORES1,ONTORES2>>();
		}
	}

	public void addImpossibleMappingFrom1(NoMappingPossible<ONTORES1> notMap) {
		impossibleMapping1.put(onto1.getURI(notMap.getConcept()), notMap);
	}

	public void addImpossibleMappingFrom2(NoMappingPossible<ONTORES2> notMap) {
		impossibleMapping2.put(onto2.getURI(notMap.getConcept()), notMap);
	}

	public void removeImpossibleMappingFrom1(ONTORES1 concept) {
		impossibleMapping1.remove(onto1.getURI(concept));
	}

	public void removeImpossibleMappingFrom2(ONTORES2 concept) {
		impossibleMapping2.remove(onto2.getURI(concept));
	}

	/** Add mappings from alignment in parameters.
	 * DO NOT MAKE A COPY OF NEW MAPPINGS, DIRECTLY USE THE SAME REFERENCE.
	 * @param alignment Another alignment.
	 */
	public void addAll(Alignment<ONTORES1, ONTORES2> alignment) {
		if(!alignment.getOnto1().equals(onto1) || !alignment.getOnto2().equals(onto2)) {
			throw new IllegalArgumentException("addAll exception: ontologies must be the same, not only the same type.");
		}

		for(Mapping<ONTORES1, ONTORES2> newMappings : alignment.getMapping()) {
			addMap(newMappings);
		}
		for(NoMappingPossible<ONTORES1> nomap : alignment.getImpossibleMapping1().values()) {
			addImpossibleMappingFrom1(nomap);
		}
		for(NoMappingPossible<ONTORES2> nomap : alignment.getImpossibleMapping2().values()) {
			addImpossibleMappingFrom2(nomap);
		}
	}

	/** Return the mapping between the specified concepts.
	 * @param cpt1 
	 * @param cpt2
	 * @return
	 */
	public SortedSet<Mapping<ONTORES1, ONTORES2>> subSet(ONTORES1 cpt1, ONTORES2 cpt2) {
		SortedSet<Mapping<ONTORES1, ONTORES2>> result = new TreeSet<Mapping<ONTORES1,ONTORES2>>();
		URI cpt1URI = onto1.getURI(cpt1);
		if(index1.containsKey(cpt1URI)) {
			for(Mapping<ONTORES1, ONTORES2> map : index1.get(cpt1URI)) {
				if(map.getSecondConcept().equals(cpt2))
					result.add(map);
			}
		}
		return result;
	}

	public boolean isImpossibleToAlign1(ONTORES1 concept) {
		return impossibleMapping1.containsKey(onto1.getURI(concept));
	}

	public boolean isImpossibleToAlign2(ONTORES2 concept) {
		return impossibleMapping2.containsKey(onto2.getURI(concept));
	}

	public boolean alignExist1(ONTORES1 concept) {
		return index1.containsKey(onto1.getURI(concept));
	}

	public boolean alignExist2(ONTORES2 concept) {
		return index2.containsKey(onto2.getURI(concept));
	}
} public class Mapping<T, V> implements Comparable<Mapping<?, ?>> {

	/** The string noted as "method" if no method was given. */
	public static String UNKNOW_METHOD = "unknow";

	private T firstConcept = null;
	private V secondConcept = null;
	private double score = 0.0;
	private MAPPING_TYPE type = MAPPING_TYPE.EQUIV;
	private String method = UNKNOW_METHOD;
	private VALIDITY validity = VALIDITY.TO_CONFIRM;
	private String comment = "";
	private Map<String, String> meta = new TreeMap<String, String>();
	private DateTime creationDate = new DateTime();

	/**
	 * Mapping type. The last is the most important, to allow to compare two
	 * types using their indexes.
	 * Note that the values in the enum are returned _in the order they are declared_ so any changes in the
	 * order declaration of values will have a direct impcat on GUI.
	 * 
	 * @author Laurent Mazuel
	 */
	public enum MAPPING_TYPE {
		EQUIV("exactMatch", SKOS.EXACT_MATCH, "equivalence", "=", "eq"), // Equivalence
		OVERLAP("closeMatch", SKOS.CLOSE_MATCH), // Overlap
		RELATED("relatedMatch", SKOS.RELATED_MATCH),
		SUBSUMEDBY("broadMatch", SKOS.BROAD_MATCH, "<"), // Subsomption by
		SUBSUMES("narrowMatch", SKOS.NARROW_MATCH, ">"), // subsumes
		// this is the generic SKOS mapping relation
		UNDEFINED("undefined", SKOS.MAPPING_RELATION),
		DISJOINT("disjoint", null); // Disjoint

		private String label = null;
		private String[] equivalentForms = null;
		private IRI skosPropertyIri;

		private MAPPING_TYPE(String label, IRI skosPropertyIri, String... equivalentForms) {
			this.label = label;
			this.equivalentForms = equivalentForms;
			this.skosPropertyIri = skosPropertyIri;
		}

		/** Return the label, for UI usage.
		 * @return
		 */
		public String getLabel() {
			return label;
		}

		/**
		 * Returns the corresponding SKOS property
		 * @return
		 */
		public IRI getSkosPropertyIri() {
			return skosPropertyIri;
		}

		/**
		 * Test if the string in parameter can be considered as equivalent of
		 * this type. Some relation type have different forms, as "equivalence",
		 * "=", "eq", "equiv", etc.
		 * 
		 * @param s
		 *            A string which may reprents this type.
		 * @return <code>true</code> if the string represents this type,
		 *         <code>false</code> otherwise.
		 */
		public boolean isEquivalentToString(String s) {
			// Test using representing string
			if (this.name().equals(s.toUpperCase()))
				return true;
			// Test using equivalent forms of this type
			for (String type : equivalentForms) {
				if (s.toLowerCase().equals(type.toLowerCase()))
					return true;
			}
			// No, sorry i didn't found it...
			return false;
		}

		/**
		 * Return the type which is represented by this string, or
		 * <code>null</code> if none was found.
		 * 
		 * @param s
		 *            A string which may be a type.
		 * @return A type if possible, <code>null</code> if not.
		 */
		public static MAPPING_TYPE getTypeFromString(String s) {
			for (MAPPING_TYPE type : MAPPING_TYPE.values()) {
				if (type.isEquivalentToString(s))
					return type;
			}
			return null;
		}

		public static MAPPING_TYPE getTypeFromSkosPredicate(IRI skosPredicate) {
			for (MAPPING_TYPE type : MAPPING_TYPE.values()) {
				if (type.getSkosPropertyIri() != null && type.getSkosPropertyIri().stringValue().equals(skosPredicate.stringValue())) {
					return type;
				}
			}
			return null;
		}
		
		
	}

	/**
	 * Validitiy of this alignment.
	 * 
	 * @author Laurent Mazuel
	 */
	public enum VALIDITY {
		VALID, TO_CONFIRM, INVALID;
	}

	/**
	 * Construct a new mapping.
	 * 
	 * @param firstConcept
	 * @param secondConcept
	 * @param score
	 * @param type
	 */
	public Mapping(T firstConcept, V secondConcept, double score,
			MAPPING_TYPE type) {
		this(firstConcept, secondConcept, score, type, UNKNOW_METHOD,
				VALIDITY.TO_CONFIRM);
	}

	/**
	 * @param firstConcept
	 * @param secondConcept
	 * @param score
	 * @param type
	 * @param method
	 */
	public Mapping(T firstConcept, V secondConcept, double score,
			MAPPING_TYPE type, String method) {
		this(firstConcept, secondConcept, score, type, method, VALIDITY.TO_CONFIRM);
	}

	/**
	 * @param firstConcept
	 * @param secondConcept
	 * @param score
	 * @param type
	 * @param method
	 * @param validity
	 */
	public Mapping(T firstConcept, V secondConcept, double score,
			MAPPING_TYPE type, String method, VALIDITY validity) {
		this(firstConcept, secondConcept, score, type, method, validity,
				new DateTime());
	}

	/**
	 * @param firstConcept
	 * @param secondConcept
	 * @param score
	 * @param type
	 * @param method
	 * @param validity
	 */
	public Mapping(T firstConcept, V secondConcept, double score,
			MAPPING_TYPE type, String method, VALIDITY validity,
			DateTime creationDate) {
		this.firstConcept = firstConcept;
		this.secondConcept = secondConcept;
		this.score = score;
		this.type = type;
		this.method = method;
		this.validity = validity;
		this.creationDate = creationDate;
	}

	/**
	 * Construct a new mapping, assuming that type is {@link MAPPING_TYPE#EQUIV}
	 * .
	 * 
	 * @param firstConcept
	 * @param secondConcept
	 * @param score
	 */
	public Mapping(T firstConcept, V secondConcept, double score) {
		this(firstConcept, secondConcept, score, MAPPING_TYPE.EQUIV);
	}

	/**
	 * Construct a new mapping, assuming that type is {@link MAPPING_TYPE#EQUIV}
	 * and score is 1.0.
	 * 
	 * @param firstConcept
	 * @param secondConcept
	 */
	public Mapping(T firstConcept, V secondConcept) {
		this(firstConcept, secondConcept, 1.0, MAPPING_TYPE.EQUIV);
	}
	
	/**
	 * Constructs a new Mapping based on another one (copy-constructor), except for the type
	 * @param otherMapping
	 */
	public Mapping(Mapping<T, V> otherMapping, MAPPING_TYPE newType) {
		this.firstConcept = otherMapping.firstConcept;
		this.secondConcept = otherMapping.secondConcept;
		this.score = otherMapping.score;
		// copy everything except the type
		this.type = newType;
		this.method = otherMapping.method;
		this.validity = otherMapping.validity;
		this.creationDate = otherMapping.creationDate;
		this.comment = otherMapping.comment;
		this.meta = otherMapping.meta;
	}
	
	/**
	 * Constructs a new Mapping based on another one (copy-constructor), except for the validity
	 * @param otherMapping
	 */
	public Mapping(Mapping<T, V> otherMapping, VALIDITY newValidity) {
		this.firstConcept = otherMapping.firstConcept;
		this.secondConcept = otherMapping.secondConcept;
		this.score = otherMapping.score;
		this.type = otherMapping.type;
		this.method = otherMapping.method;
		// copy everything except the validity
		this.validity = newValidity;
		this.creationDate = otherMapping.creationDate;
		this.comment = otherMapping.comment;
		this.meta = otherMapping.meta;
	}

	/**
	 * @return the firstConcept
	 */
	public T getFirstConcept() {
		return firstConcept;
	}

	/**
	 * @return the score
	 */
	public double getScore() {
		return score;
	}

	/**
	 * @return the secondConcept
	 */
	public V getSecondConcept() {
		return secondConcept;
	}

	/**
	 * @return the type
	 */
	public MAPPING_TYPE getType() {
		return type;
	}
	
//	public void setType(MAPPING_TYPE type) {
//		this.type = type;
//	}

	public Map<String, String> getMeta() {
		return meta;
	}

	public void setMeta(Map<String, String> meta) {
		this.meta = meta;
	}

	void setMethod(String method) {
		this.method = method;
	}

	/**
	 * @return the method used to compute this mapping
	 */
	public String getMethod() {
		return method;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((comment == null) ? 0 : comment.hashCode());
		result = prime * result
				+ ((firstConcept == null) ? 0 : firstConcept.hashCode());
		result = prime * result + ((meta == null) ? 0 : meta.hashCode());
		result = prime * result + ((method == null) ? 0 : method.hashCode());
		long temp;
		temp = Double.doubleToLongBits(score);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result
				+ ((secondConcept == null) ? 0 : secondConcept.hashCode());
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		result = prime * result
				+ ((validity == null) ? 0 : validity.hashCode());
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Mapping other = (Mapping) obj;
		if (comment == null) {
			if (other.comment != null)
				return false;
		} else if (!comment.equals(other.comment))
			return false;
		if (firstConcept == null) {
			if (other.firstConcept != null)
				return false;
		} else if (!firstConcept.equals(other.firstConcept))
			return false;
		if (meta == null) {
			if (other.meta != null)
				return false;
		} else if (!meta.equals(other.meta))
			return false;
		if (method == null) {
			if (other.method != null)
				return false;
		} else if (!method.equals(other.method))
			return false;
		if (Double.doubleToLongBits(score) != Double
				.doubleToLongBits(other.score))
			return false;
		if (secondConcept == null) {
			if (other.secondConcept != null)
				return false;
		} else if (!secondConcept.equals(other.secondConcept))
			return false;
		if (type == null) {
			if (other.type != null)
				return false;
		} else if (!type.equals(other.type))
			return false;
		if (validity == null) {
			if (other.validity != null)
				return false;
		} else if (!validity.equals(other.validity))
			return false;
		return true;
	}

	/**
	 * La méthode compareTo n'utilise que des caractéristiques non éditables dans OnaGUI
	 * Pour ne pas que la ligne bouge si on édite le type ou la validité.
	 */
	@Override
	public int compareTo(Mapping<?, ?> o) {
		// Consistance avec l'égalité
		if (this.equals(o))
			return 0;

		// Comparaison sur le premier concept
		int compare = firstConcept.toString().compareTo(o.firstConcept.toString());
		if(compare != 0) {
			return compare;
		}
		// comparaison sur le deuxieme concept
		compare = secondConcept.toString().compareTo(o.secondConcept.toString());
		if(compare != 0) {
			return compare;
		}		

		// Ici les concepts sont identiques, on regarde le score
		compare = Double.valueOf(this.score).compareTo(Double.valueOf(o.score));
		if (compare != 0)
			return compare;

		// concept 1, concept 2 et score identique
		// on se rabat sur un toString
		return this.toString().compareTo(o.toString());		
	}
	
	
	public void setComment(String comment) {
		this.comment = comment;
	}

	public String getComment() {
		return comment;
	}

	/**
	 * @return the validity
	 */
	public VALIDITY getValidity() {
		return validity;
	}

	/**
	 * @param validity
	 *            the validity to set
	 */
//	public void setValidity(VALIDITY validity) {
//		this.validity = validity;
//	}

	/**
	 * @return The creation ddate of this instance.
	 */
	public DateTime getCreationDate() {
		return creationDate;
	}

	@Override
	public String toString() {
		StringBuffer buf = new StringBuffer("[");
		buf.append(firstConcept);
		buf.append('-');
		buf.append(type);
		buf.append('-');
		buf.append(secondConcept);
		buf.append(", SCR=");
		buf.append(score);
		buf.append(", method=");
		buf.append(method);
		buf.append(", valid=");
		buf.append(validity);
		buf.append(", creation date=");
		ISODateTimeFormat.basicDateTimeNoMillis().printTo(buf, creationDate);
		buf.append("]");
		return buf.toString();
	}

} public enum MAPPING_TYPE {
	EQUIV("exactMatch", SKOS.EXACT_MATCH, "equivalence", "=", "eq"), // Equivalence
	OVERLAP("closeMatch", SKOS.CLOSE_MATCH), // Overlap
	RELATED("relatedMatch", SKOS.RELATED_MATCH),
	SUBSUMEDBY("broadMatch", SKOS.BROAD_MATCH, "<"), // Subsomption by
	SUBSUMES("narrowMatch", SKOS.NARROW_MATCH, ">"), // subsumes
	// this is the generic SKOS mapping relation
	UNDEFINED("undefined", SKOS.MAPPING_RELATION),
	DISJOINT("disjoint", null); // Disjoint

	private String label = null;
	private String[] equivalentForms = null;
	private IRI skosPropertyIri;

	private MAPPING_TYPE(String label, IRI skosPropertyIri, String... equivalentForms) {
		this.label = label;
		this.equivalentForms = equivalentForms;
		this.skosPropertyIri = skosPropertyIri;
	}

	/** Return the label, for UI usage.
	 * @return
	 */
	public String getLabel() {
		return label;
	}

	/**
	 * Returns the corresponding SKOS property
	 * @return
	 */
	public IRI getSkosPropertyIri() {
		return skosPropertyIri;
	}

	/**
	 * Test if the string in parameter can be considered as equivalent of
	 * this type. Some relation type have different forms, as "equivalence",
	 * "=", "eq", "equiv", etc.
	 * 
	 * @param s
	 *            A string which may reprents this type.
	 * @return <code>true</code> if the string represents this type,
	 *         <code>false</code> otherwise.
	 */
	public boolean isEquivalentToString(String s) {
		// Test using representing string
		if (this.name().equals(s.toUpperCase()))
			return true;
		// Test using equivalent forms of this type
		for (String type : equivalentForms) {
			if (s.toLowerCase().equals(type.toLowerCase()))
				return true;
		}
		// No, sorry i didn't found it...
		return false;
	}

	/**
	 * Return the type which is represented by this string, or
	 * <code>null</code> if none was found.
	 * 
	 * @param s
	 *            A string which may be a type.
	 * @return A type if possible, <code>null</code> if not.
	 */
	public static MAPPING_TYPE getTypeFromString(String s) {
		for (MAPPING_TYPE type : MAPPING_TYPE.values()) {
			if (type.isEquivalentToString(s))
				return type;
		}
		return null;
	}

	public static MAPPING_TYPE getTypeFromSkosPredicate(IRI skosPredicate) {
		for (MAPPING_TYPE type : MAPPING_TYPE.values()) {
			if (type.getSkosPropertyIri() != null && type.getSkosPropertyIri().stringValue().equals(skosPredicate.stringValue())) {
				return type;
			}
		}
		return null;
	}
		
		
} public enum VALIDITY {
	VALID, TO_CONFIRM, INVALID;
} public class NoMappingPossible<T> {
	
	private T concept = null;
	private String comment = null;
	
	/**
	 * @param concept
	 */
	public NoMappingPossible(T concept) {
		this.concept = concept;
	}
	
	public T getConcept() {
		return concept;
	}
	
	public void setComment(String comment) {
		this.comment = comment;
	}
	
	public String getComment() {
		return comment;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + ((concept == null) ? 0 : concept.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final NoMappingPossible other = (NoMappingPossible) obj;
		if (concept == null) {
			if (other.concept != null)
				return false;
		} else if (!concept.equals(other.concept))
			return false;
		return true;
	}

	@Override
	public String toString() {
		StringBuffer buf = new StringBuffer();
		buf.append("No mapping possible with concept: ");
		buf.append(concept);
		return buf.toString();
	}
	
} public interface OntoContainer<ONTORES> {
	
	/* **************** *
	 * Partie technique *
	 * **************** */
	
	/** Une chaine de caractere representant le type de formalisme de cette ontologie.
	 * @return Une chaine de caractere representant le type de formalisme de cette ontologie.
	 */
	public String getFormalism();
	
	/** Renvoie l'URI d'un concept.
	 * @param cpt Un concept.
	 * @return L'URI de ce concept.
	 * @see #getConceptFromURI(URI)
	 */
	public URI getURI(ONTORES cpt);
	
	/** L'ensemble des concepts (classes) de ce modèle.
	 * @return L'ensemble des concepts (classes) de ce modèle.
	 */
	public Set<ONTORES> getAllConcepts();
	
	/** L'ensemble des propriétés de ce modèle.
	 * @return L'ensemble des propriétés de ce modèle.
	 */
	public Set<ONTORES> getAllProperties();
	
	/** Get Annotation of this concept
	 * @param cpt a Concept
	 * @return A set of URIs
	 */
	public Set<String> getAnnotations(ONTORES cpt);
	
	/** Get labels for this predicate
	 * @param cpt The concept
	 * @param prop The predicate
	 * @return The set of labels
	 */
	public Set<String> getLabels(ONTORES cpt, String prop);
		
	/** Recupère un concept à partir de l'URI.
	 * @param uri L'URI d'un concept.
	 * @return Un concept.
	 * @see #getURI()
	 */
	public ONTORES getConceptFromURI(URI uri);
		
	/** Renvoie l'URI de l'ontologie.
	 * @return L'URI de l'ontologie.
	 */
	public URI getURI();
	
	/** Renvoie la racine de l'ontologie.
	 * @return La racine de l'ontologie.
	 */
	public ONTORES getRoot();
	
	/** Decrit si le concept courant est une instance ou pas.
	 * @param cpt Un concept
	 * @return <code>true</code> si instance, <code>false</code> sinon. */
	public boolean isIndividual(ONTORES cpt);
	
	/** Les fils de ce concept.
	 * @param cpt Un concept
	 * @return Les fils de ce concept.
	 */
	public Set<ONTORES> getChildren(ONTORES cpt);
	
	/**
	 * Renvoi la date de modification du concept passé en paramètre
	 * @param cpt
	 * @return
	 */
	
	public Optional<Date> getModifiedDate(ONTORES cpt);
	
	/** Les parents de ce concept
	 * @param cpt Un concept
	 * @return Les parents directs de ce concept
	 */
	public Set<ONTORES> getParents(ONTORES cpt);
	
	/** A visitor which accept a callback on the concept 
	 * 
	 * @param visitor A visitor implementation
	 */
	public void accept(OntoVisitor<ONTORES> visitor);
	
	/* *************** *
	 * Partie lexicale *
	 * *************** */
	
	/** Renvoie la liste des langages utilisés dans les labels.
	 * @return
	 */
	public SortedSet<String> getAllLanguageInLabels();
	
	/** Retourne l'ensemble des termes préférés de ce concept de la langue en parametre.
	 * En general, le resultat est un singleton. 
	 * @param cpt Un concept.
	 * @param lang La langue demandée, format normalisé selon l'entrée "xml:lang". 
	 * La chaine vide "" pour n'avoir que les noeuds sans précision.
	 * @return L'ensemble des termes préférés de ce concept.
	 * @throws IllegalArgumentException Si l'un des paramètres est <code>null</code>.
	 */
	public Set<String> getPrefLabels(ONTORES cpt, String lang);
	
	/** Retourne l'ensemble des termes préférés de ce concept, quelque soit la langue.
	 * @param cpt Un concept.
	 * @return L'ensemble des termes préférés de ce concept.
	 * @throws IllegalArgumentException Si l'un des paramètres est <code>null</code>.
	 */
	public Set<String> getPrefLabels(ONTORES cpt);

	/** Retourne l'ensemble des termes alternatifs de ce concept de la langue en parametre.
	 * @param cpt Un concept.
	 * @param lang La langue demandée, format normalisé selon l'entrée "xml:lang". 
	 * La chaine vide "" pour n'avoir que les noeuds sans précision.
	 * @return L'ensemble des termes alternatifs de ce concept.
	 * @throws IllegalArgumentException Si l'un des paramètres est <code>null</code>.
	 */
	public Set<String> getAltLabels(ONTORES cpt, String lang);
	
	/** Retourne l'ensemble des termes alternatifs de ce concept, quelque soit la langue.
	 * @param cpt Un concept.
	 * @return L'ensemble des termes alternatifs de ce concept.
	 * @throws IllegalArgumentException Si l'un des paramètres est <code>null</code>.
	 */
	public Set<String> getAltLabels(ONTORES cpt);
	
} public class CSVImpl implements IOAlignment {
	
	private static final String TITLES = "Fragment 1;"+
			"Fragment 2;"+
			"Alignement;"+
			"Preflabels 1;"+
			"Preflabels 2;"+
			"Altlabels 1;"+
			"Altlabels 2;"+
			"Method;"+
			"Validity;"+
			"Date";
	
	private static final DateTimeFormatter TIME_FORMATTER = ISODateTimeFormat.dateTimeNoMillis();
	
	@Override
	public AlignmentFormat getFormat() {
		return AlignmentFormat.CSV;
	}

	@Override
	public <ONTORES1, ONTORES2> Alignment<ONTORES1, ONTORES2> loadAlignment(
			OntoContainer<ONTORES1> onto1, OntoContainer<ONTORES2> onto2,
			File file) throws IOException {

		System.out.println("Chargement à partir du fichier.");

		// Un reader sur le fichier CSV
		BufferedReader reader = Files.newBufferedReader(Paths.get(file.toURI()), ONAGUI_CHARSET);

		// Un peu de trifouillage ontologie
		Alignment<ONTORES1, ONTORES2> alignment = new Alignment<ONTORES1, ONTORES2>(
				onto1, onto2);

		String line = null;
		while ((line = reader.readLine()) != null) {
			if(line.equals(TITLES)) continue;
			String[] scan = line.split(";");
			if (scan.length == 0)
				continue; // Ligne vide, pourquoi pas...
			
			ONTORES1 res1 = null;
			try {
				res1 = onto1.getConceptFromURI(URI.create(scan[0]));
			} catch(Exception e) {}
			ONTORES2 res2 = null;
			try {
				res2 = onto2.getConceptFromURI(URI.create(scan[1]));;
			} catch(Exception e) {}

			// La case 3, type d'alignement
			if(scan[2].equals(CANNOT_BE_ALIGNED)) {
				if(res1 != null && res2 == null) {
					alignment.addImpossibleMappingFrom1(new NoMappingPossible<ONTORES1>(res1));
					continue;
				}
				else if(res1 == null && res2 != null) {
					alignment.addImpossibleMappingFrom2(new NoMappingPossible<ONTORES2>(res2));
					continue;
				}
			}
			if(res1 == null || res2 == null) {
				System.err.println("Invalid line: " + line);
				continue;
			}

			// La case 3, type d'alignement
			// by default, the undefined type
			MAPPING_TYPE readedType = (scan.length>=3)?extractAlignmentType(scan[2]):MAPPING_TYPE.UNDEFINED;
			// Skip labels (4 cases)
			String method = (scan.length>=8)?scan[7]:Mapping.UNKNOW_METHOD;
			double score = (scan.length>=9)?Double.valueOf(scan[8]):1.0;
			VALIDITY validity = (scan.length>=10)?VALIDITY.valueOf(scan[9]):VALIDITY.VALID;
			DateTime date = (scan.length>=11)?TIME_FORMATTER.parseDateTime(scan[10]):DateTime.now();

			Mapping<ONTORES1, ONTORES2> map = new Mapping<ONTORES1, ONTORES2>(
					res1, res2, score, readedType, method, validity, date);
			alignment.addMap(map);
		}
		reader.close();
		return alignment;
	}
	
	private static MAPPING_TYPE extractAlignmentType(String alignMethod) {
		try {
			return MAPPING_TYPE.getTypeFromString(alignMethod);
		} catch (Exception e) {}
		return MAPPING_TYPE.EQUIV;
	}

	@Override
	public <ONTORES1, ONTORES2> void saveAlignment(
			Alignment<ONTORES1, ONTORES2> alignment, String pathToSave,
			VALIDITY validityWanted) throws IOException {

		BufferedWriter writer = Files.newBufferedWriter(Paths.get(pathToSave), ONAGUI_CHARSET);
		OntoContainer<ONTORES1> onto1 = alignment.getOnto1();
		OntoContainer<ONTORES2> onto2 = alignment.getOnto2();
		StringBuilder buf = null;

		writer.write(TITLES);
		writer.newLine();
		
		for (Mapping<ONTORES1, ONTORES2> map : alignment.getMapping()) {
			if(validityWanted != null && !map.getValidity().equals(validityWanted))
				continue;
			buf = new StringBuilder();
			final ONTORES1 firstConcept = map.getFirstConcept();
			final ONTORES2 secondConcept = map.getSecondConcept();
			// First concept fragment
			buf.append(onto1.getURI(firstConcept).toString());
			buf.append(';');
			// Second concept fragment
			buf.append(onto2.getURI(secondConcept).toString());
			buf.append(';');
			// Alignement symbol
			buf.append(map.getType().name());
			buf.append(';');
			// Preflabels of concept 1
			buf.append(CSVImpl.join(onto1.getPrefLabels(firstConcept), ","));
			buf.append(";");
			// Preflabels of concept 2
			buf.append(CSVImpl.join(onto2.getPrefLabels(secondConcept), ","));
			buf.append(";");
			// Altlabels of concept 1
			buf.append(CSVImpl.join(onto1.getAltLabels(firstConcept), ","));
			buf.append(";");
			// Altlabels of concept 2
			buf.append(CSVImpl.join(onto2.getAltLabels(secondConcept), ","));
			buf.append(";");
			// Method
			buf.append(map.getMethod());
			buf.append(";");
			// Score
			buf.append(map.getScore());
			buf.append(";");
			// Validity
			buf.append(map.getValidity());
			buf.append(";");
			// Date
			buf.append(TIME_FORMATTER.print(map.getCreationDate()));
			// Writing this line
			writer.write(buf.toString());
			writer.newLine();
		}
		for(NoMappingPossible<ONTORES1> nomap :	alignment.getImpossibleMapping1().values()) {
			buf = new StringBuilder();
			buf.append(onto1.getURI(nomap.getConcept()).toString());
			buf.append(";;");
			buf.append(CANNOT_BE_ALIGNED);
			writer.write(buf.toString());
			writer.newLine();
		}
		for(NoMappingPossible<ONTORES2> nomap :	alignment.getImpossibleMapping2().values()) {
			buf = new StringBuilder();
			buf.append(";");
			buf.append(onto2.getURI(nomap.getConcept()).toString());
			buf.append(";");
			buf.append(CANNOT_BE_ALIGNED);
			writer.write(buf.toString());
			writer.newLine();
		}
		writer.flush();
		writer.close();
	}

	public static String join(Iterable<? extends Object> pColl, String separator) {
		Iterator<? extends Object> oIter;
		if (pColl == null || (!(oIter = pColl.iterator()).hasNext()))
			return "";
		StringBuilder oBuilder = new StringBuilder(String.valueOf(oIter.next()));
		while (oIter.hasNext())
			oBuilder.append(separator).append(oIter.next());
		return oBuilder.toString();
	}

} public class EuzenatRDFImpl implements IOAlignment {

	// Liste des namespaces
	public static final String ALIGN_NS = "http://knowledgeweb.semanticweb.org/heterogeneity/alignment#";
	public static final String DCTERMS_NS = "http://purl.org/dc/terms/";

	// Type et propriété conforme à J.Euzenat
	public static IRI XML_PROPERTY =SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "xml");
	public static IRI LEVEL_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "level");
	public static IRI TYPE_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "type");
	public static IRI LOCATION_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "location");
	public static IRI FORMALISM_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "formalism");
	public static IRI ENTITY1_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "entity1");
	public static IRI ENTITY2_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "entity2");
	public static IRI MEASURE_PROPERTY =SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "measure");
	public static IRI RELATION_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "relation");
	public static IRI ONTO1_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "onto1");
	public static IRI ONTO2_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "onto2");
	public static IRI MAP_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "map");
	public static IRI METHOD_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "method");
	public static IRI VALID_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "validity");
	public static IRI CREATION_DATE_PROPERTY = SimpleValueFactory.getInstance().createIRI(DCTERMS_NS + "date");
	public static IRI METAMETHOD_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "metamethod");
	public static IRI ALIGNMENT_TYPE = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "Alignment");
	public static IRI ONTOLOGY_TYPE = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "Ontology");
	//	private Resource formalismType = null;
	public static IRI CELL_TYPE = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "Cell");
	// Type et propriétés perso
	public static IRI NOMAP_PROPERTY = SimpleValueFactory.getInstance().createIRI(ALIGN_NS + "nomap");

	private DateTimeFormatter timeFormatter = null;
	
	private IOEventManager ioEventManager = null;

	/** Default constructor.
	 * All warning messages are sended to stderr.
	 * 
	 */
	public EuzenatRDFImpl() {
		this(new IOEventManager() {			
			@Override
			public void outputEvent(String msg) {
				System.err.println(msg);
			}
			
			@Override
			public void inputEvent(String msg) {
				System.err.println(msg);
			}
		});
	}
	
	/** Main constructor, with EventManager for warning
	 * @param ioe
	 */
	public EuzenatRDFImpl(IOEventManager ioe) {
		this.ioEventManager = ioe;
		timeFormatter = ISODateTimeFormat.dateTimeNoMillis();
	}
	
	@Override
	public AlignmentFormat getFormat() {
		return AlignmentFormat.EDOAL;
	}

	/* (non-Javadoc)
	 * @see fr.onagui.alignment.io.IOAlignment#loadAlignment(fr.onagui.alignment.OntoContainer, fr.onagui.alignment.OntoContainer, java.io.File)
	 */
	@Override
	public <ONTORES1, ONTORES2> Alignment<ONTORES1, ONTORES2> loadAlignment(
			OntoContainer<ONTORES1> onto1, OntoContainer<ONTORES2> onto2,
			File file) {

		if (file == null || !file.exists() || !file.isFile())
			throw new IllegalArgumentException(
					"File parameter is not a valid existing file.");

		Model model = new LinkedHashModel();
		
		// read the RDF/XML file
		try {
			RDFFormat format = Rio.getParserFormatForFileName(file.getAbsolutePath()).orElse(RDFFormat.RDFXML);
			model = Rio.parse(new FileInputStream(file), file.getAbsolutePath(), format);
		} catch (FileNotFoundException e) {
			// Impossible, j'ai testé l'existence au dessus...
			e.printStackTrace();
			throw new IllegalArgumentException("Unable to read file. Right problem?");
		} catch (RDFParseException e) {
			throw new RuntimeException("Unable to read RDF file "+file.getAbsolutePath(), e);
		} catch (UnsupportedRDFormatException e) {
			throw new RuntimeException("Unable to read RDF file "+file.getAbsolutePath(), e);
		} catch (IOException e) {
			throw new RuntimeException("Unable to read RDF file "+file.getAbsolutePath(), e);
		}
		
		
		Alignment<ONTORES1, ONTORES2> alignment = new Alignment<ONTORES1, ONTORES2>(
				onto1, onto2);
		
		
		for (Resource alignement: model.filter(null, RDF.TYPE, ALIGNMENT_TYPE).subjects()) {

			// Ajouter chaque mapping
			for (Value cellValue: model.filter(alignement, MAP_PROPERTY, null).objects()) {
				Resource cellNode = (Resource)cellValue;
				Resource entity1 = (Resource)getRequiredProperty(cellNode, ENTITY1_PROPERTY, model);
				Resource entity2 = (Resource)getRequiredProperty(cellNode, ENTITY2_PROPERTY, model);
				Value measureValue =getRequiredProperty(cellNode, MEASURE_PROPERTY, model);
				
				
				double score = Double.valueOf(((Literal)measureValue).stringValue());

				Value relationStmt = getRequiredProperty(cellNode, RELATION_PROPERTY, model);

				final String relationStringFromRDF = relationStmt.stringValue();
				MAPPING_TYPE type = MAPPING_TYPE
						.getTypeFromString(relationStringFromRDF);
				// FIXME Si je trouves une "Cell" mais que je ne reconnais pas
				// le type, j'affiche en erreur et je met le type de mapping par défaut
				if (type == null) {
					type = MAPPING_TYPE.UNDEFINED;
					System.err.println("Relation type was not recognized: "
							+ relationStringFromRDF);
				}
				// Manage method

				Value methodValue = getProperty(cellNode,METHOD_PROPERTY,model);
				String method = (methodValue != null) ? methodValue.stringValue()
						: Mapping.UNKNOW_METHOD;
				
				// Manage validity				
				Value validValue = getProperty(cellNode,VALID_PROPERTY,model);
				VALIDITY valid = (validValue != null) ? VALIDITY
						.valueOf(validValue.stringValue()) : VALIDITY.TO_CONFIRM;
						
				// Manage creation date
				Value creationDateValue = getProperty(cellNode,CREATION_DATE_PROPERTY,model);
				DateTime date = (creationDateValue != null) ? timeFormatter
						.parseDateTime(creationDateValue.stringValue())
						: new DateTime();
						
				// Manage meta				
				Resource metaRes = (Resource)getProperty(cellNode,METAMETHOD_PROPERTY,model);
				Map<String, String> metaMap = new TreeMap<String, String>();
				if (metaRes != null) {
					
					for (Statement metaStmt: model.filter(metaRes, null,null)) {
						// Manage predidate
						IRI metaProp = metaStmt.getPredicate();
						String key = metaProp.getLocalName();
						// Manage object
						String value = metaStmt.getObject().stringValue();
						// Adding to metamap
						metaMap.put(key, value);
					}
				}
				
				// Manage comment
				Value commentValue = getProperty(cellNode,RDFS.COMMENT,model);

				try {
					String uri1 = entity1.stringValue();
					String uri2 = entity2.stringValue();
					// Prepare errMSg, maybe....
					String errMsg = "Loading alignment between " + uri1
							+ " and " + uri2
							+ " failed because this concepts does not exist: ";
					ONTORES1 cpt1 = onto1.getConceptFromURI(new URI(uri1));
					if (cpt1 == null) {
						ioEventManager.inputEvent(errMsg + uri1);
						continue;
					}
					ONTORES2 cpt2 = onto2.getConceptFromURI(new URI(uri2));
					if (cpt2 == null) {
						ioEventManager.inputEvent(errMsg + uri2);
						continue;
					}

					Mapping<ONTORES1, ONTORES2> map = new Mapping<ONTORES1, ONTORES2>(
							cpt1, cpt2, score, type, method, valid, date);
					if (commentValue != null) {
						map.setComment(commentValue.stringValue());
					}
					if (!metaMap.isEmpty()) {
						map.setMeta(metaMap);
					}

					alignment.addMap(map);
				} catch (URISyntaxException e) {
					ioEventManager.inputEvent("One the value is not a valid uri");
					e.printStackTrace();
				}
			}

			// Ajouter chaque mapping impossible
			Model iterator = model.filter(alignement, NOMAP_PROPERTY, null);
			for(Statement stat:iterator) {
				Statement cellNodeStmt = stat;
				Resource cellNode = (Resource)cellNodeStmt.getObject();
				// FIXME "cellNode" devrait être un un noeud de Type Cell.
				// Verification à faire?
				Resource entity1 = (Resource)getProperty(cellNode, ENTITY1_PROPERTY, model);
				Resource entity2 = (Resource)getProperty(cellNode, ENTITY2_PROPERTY, model);

				try {
					if (entity1 != null) {
						ONTORES1 cpt1 = onto1.getConceptFromURI(new URI(entity1.stringValue()));
						NoMappingPossible<ONTORES1> nomap = new NoMappingPossible<ONTORES1>(
								cpt1);
						alignment.addImpossibleMappingFrom1(nomap);
					} else {
						ONTORES2 cpt2 = onto2.getConceptFromURI(new URI(entity2.stringValue()));
						NoMappingPossible<ONTORES2> nomap = new NoMappingPossible<ONTORES2>(
								cpt2);
						alignment.addImpossibleMappingFrom2(nomap);
					}
				} catch (URISyntaxException e) {
					ioEventManager.inputEvent("Your URI was not valid, statement ignored: "
									+ cellNodeStmt);
					e.printStackTrace();
				}
			}
		}
		return alignment;
	}
	
	public Value getRequiredProperty(Resource node, IRI property, Model model){				
		for (Statement statement:model.filter(node, property, null)) {
			return statement.getObject();
		} 
		throw new RuntimeException("Unable to find required property "+property+" on resource "+node);
	}
	
	public Value getProperty(Resource node, IRI property, Model model){				
		for (Statement statement:model.filter(node, property, null)) {
			return statement.getObject();
		} 
		return null;
	}

	/* (non-Javadoc)
	 * @see fr.onagui.alignment.io.IOAlignment#saveAlignment(fr.onagui.alignment.Alignment, java.lang.String, fr.onagui.alignment.Mapping.VALIDITY)
	 */
	@Override
	public <ONTORES1, ONTORES2> void saveAlignment(
			Alignment<ONTORES1, ONTORES2> alignment, String pathToSave,
			VALIDITY validityWanted) throws IOException {
		
		Model model =new LinkedHashModel();
		ValueFactory factory = SimpleValueFactory.getInstance();
		model.setNamespace("",ALIGN_NS);
		model.setNamespace("dcterms",DCTERMS_NS);
		

		// Creation de notre arbre
		BNode rootAlignment= createAnonymousType(model,factory,ALIGNMENT_TYPE);
		model.add(rootAlignment,XML_PROPERTY, factory.createLiteral("yes"));
		model.add(rootAlignment,LEVEL_PROPERTY, factory.createLiteral("0"));
		model.add(rootAlignment,TYPE_PROPERTY, factory.createLiteral("**"));
		final OntoContainer<ONTORES1> onto1 = alignment.getOnto1();
		final OntoContainer<ONTORES2> onto2 = alignment.getOnto2();

		// Meta-data about ontology
		buildOntoMetaData(model, rootAlignment,factory,onto1, 1);
		buildOntoMetaData(model, rootAlignment, factory,onto2, 2);
		// Build the mapping nodes
		for (Mapping<ONTORES1, ONTORES2> mapping : alignment.getMapping()) {
			buildCellNode(onto1, onto2, mapping, model, rootAlignment, validityWanted);
		}

		// Build the no mapping nodes
		for (NoMappingPossible<ONTORES1> noMap : alignment
				.getImpossibleMapping1().values()) {
			buildNoMapNode(onto1, noMap, 1, model, factory, rootAlignment);
		}
		for (NoMappingPossible<ONTORES2> noMap : alignment
				.getImpossibleMapping2().values()) {
			buildNoMapNode(onto2, noMap, 2, model,factory, rootAlignment);
		}
		
		
		
		FileOutputStream stream = new FileOutputStream(pathToSave);
	
		RDFWriter prettyWriter = new RDFXMLPrettyWriter(stream);
		Rio.write(model, prettyWriter);
		
		
	}

	private static BNode createAnonymousType(Model model, ValueFactory factory, IRI type) {
		BNode rootAlignment= factory.createBNode();
		model.add(rootAlignment, RDF.TYPE, type);
		return rootAlignment;
		
	}

	
	private <ONTORES> void buildOntoMetaData(Model model, Resource root, ValueFactory factory,
			OntoContainer<ONTORES> onto, int number) {
		
		IRI ontologyResource = factory.createIRI(ALIGN_NS + onto.getURI());
		
		if (number == 1) {			
			model.add(root, ONTO1_PROPERTY, ontologyResource);			
		} else {
			model.add(root, ONTO2_PROPERTY, ontologyResource);
		}
		
		model.add(ontologyResource, RDF.TYPE, ONTOLOGY_TYPE);
		model.add(ontologyResource, LOCATION_PROPERTY,factory.createLiteral(onto.getURI().toString()));
		model.add(ontologyResource, FORMALISM_PROPERTY,factory.createLiteral(onto.getFormalism()));
		
	}

	private <ONTORES1, ONTORES2> void buildCellNode(
			OntoContainer<ONTORES1> onto1,
			OntoContainer<ONTORES2> onto2,
			Mapping<ONTORES1, ONTORES2> mapping,
			Model model,
			Resource alignmentRoot,
			VALIDITY validityWanted
	) {
		ValueFactory factory = SimpleValueFactory.getInstance();
		
		VALIDITY currentMappingValidity = mapping.getValidity();
		if (validityWanted != null && !currentMappingValidity.equals(validityWanted))
			return;

		// ordre d'insertion des proprietes necessaires pour avoir une "belle"
		// ecriture dans le fichier RDF/XML
		
		// on créé le noeud correspondant a la Cell		
		BNode cellNode = factory.createBNode();
		// on relie l'objet "Alignement" a ce noeud par la propriete "map"
		model.add(alignmentRoot, MAP_PROPERTY,cellNode);
		// on donne le type de l'objet "Cell"
		model.add(cellNode, RDF.TYPE, CELL_TYPE);
		
		IRI res1 = factory.createIRI(onto1.getURI(
				mapping.getFirstConcept()).toString());
		IRI res2 = factory.createIRI(onto2.getURI(
				mapping.getSecondConcept()).toString());

		
		final String comment = mapping.getComment();
		if (comment != null && !"".equals(comment)) {
			model.add(cellNode, RDFS.COMMENT,factory.createLiteral(comment));
		}
		model.add(cellNode, VALID_PROPERTY,factory.createLiteral(currentMappingValidity.toString()));
		model.add(cellNode, RELATION_PROPERTY,factory.createLiteral(mapping.getType().toString()));
		model.add(cellNode, MEASURE_PROPERTY,factory.createLiteral((float) mapping.getScore()));
		model.add(cellNode, ENTITY2_PROPERTY,res2);
		model.add(cellNode, ENTITY1_PROPERTY,res1);
		
		// we force the date format to be exactly what we want
		// see http://stackoverflow.com/questions/10614771/java-simpledateformat-pattern-for-w3c-xml-dates-with-timezone
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
		model.add(cellNode, CREATION_DATE_PROPERTY,factory.createLiteral(sdf.format(mapping.getCreationDate().toDate()), XMLSchema.DATETIME));
		
		
		final String method = mapping.getMethod();
		final Map<String, String> metamap = mapping.getMeta();
		if (!method.equals(Mapping.UNKNOW_METHOD)) {
			model.add(cellNode, METHOD_PROPERTY,factory.createLiteral(method));
		}
		if (!metamap.isEmpty()) {
			Resource metaRoot = factory.createBNode();
			model.add(cellNode, METAMETHOD_PROPERTY,metaRoot);
			for (Map.Entry<String, String> entry : metamap.entrySet()) {
				IRI localProp = factory.createIRI(ALIGN_NS
						+ entry.getKey());
				model.add(metaRoot, localProp,factory.createLiteral(entry.getValue()));
			}			
		}	
		
	}

	private <ONTORES> void buildNoMapNode(OntoContainer<ONTORES> onto,
			NoMappingPossible<ONTORES> noMap, int number, Model model, ValueFactory factory,
			Resource alignmentRoot) {
		
		// ordre d'insertion des proprietes necessaires pour avoir une "belle"
		// ecriture dans le fichier RDF/XML
		
		// on créé le noeud correspondant a la Cell		
		BNode cellNode = factory.createBNode();
		// on relie l'objet "Alignement" a ce noeud par la propriete "nomap"
		model.add(alignmentRoot,NOMAP_PROPERTY,cellNode);
		// on donne le type de l'objet "Cell"
		model.add(cellNode, RDF.TYPE, CELL_TYPE);		
		
		IRI res1 = factory.createIRI(onto.getURI(noMap.getConcept())
				.toString());
		if (number == 1)
			model.add(cellNode,ENTITY1_PROPERTY,res1);
		else
			model.add(cellNode,ENTITY2_PROPERTY,res1);
		
	}
} public interface IOAlignment {
	
	public static final Charset ONAGUI_CHARSET = Charset.forName("UTF-8");
	
	/** Allows other class to follow what happens during "save" and "load"
	 * @author Laurent Mazuel
	 */
	public interface IOEventManager {
		
		public void inputEvent(String msg);
		public void outputEvent(String msg);
	}

	public static String CANNOT_BE_ALIGNED = "Non alignable";

	/**
	 * Load an alignment from an XML file.
	 * 
	 * @param <ONTORES1>
	 *            Primitive type of ontology 1
	 * @param <ONTORES2>
	 *            Primitive type of ontology 2
	 * @param onto1
	 *            The ontology 1
	 * @param onto2
	 *            The ontology 2
	 * @param file
	 *            A XML file which contains an alignement.
	 * @return An alignment instance.
	 */
	public <ONTORES1, ONTORES2> Alignment<ONTORES1, ONTORES2> loadAlignment(
			OntoContainer<ONTORES1> onto1, 
			OntoContainer<ONTORES2> onto2,
			File file
	) throws IOException;

	public <ONTORES1, ONTORES2> void saveAlignment(
			Alignment<ONTORES1, ONTORES2> alignment,
			String pathToSave,
			VALIDITY validityWanted
	) throws IOException;
	
	/**
	 * Returns the alignment format produced by this implementation
	 * @return
	 */
	public AlignmentFormat getFormat();

} public class SkosImpl implements IOAlignment {

	public static final String UNKNOWN_METHOD = "unknown";

	// Type et propriétés perso
	private IOEventManager ioEventManager = null;

	/** Default constructor.
	 * All warning messages are sended to stderr.
	 * 
	 */
	public SkosImpl() {
		this(new IOEventManager() {			
			@Override
			public void outputEvent(String msg) {
				System.err.println(msg);
			}
			
			@Override
			public void inputEvent(String msg) {
				System.err.println(msg);
			}
		});
	}
	
	
	public SkosImpl(IOEventManager ioe) {
		this.ioEventManager = ioe;
	}
	
	@Override
	public AlignmentFormat getFormat() {
		return AlignmentFormat.SKOS;
	}
	
	/* (non-Javadoc)
	 * @see fr.onagui.alignment.io.IOAlignment#loadAlignment(fr.onagui.alignment.OntoContainer, fr.onagui.alignment.OntoContainer, java.io.File)
	 */
	@Override
	public <ONTORES1, ONTORES2> Alignment<ONTORES1, ONTORES2> loadAlignment(
			OntoContainer<ONTORES1> onto1,
			OntoContainer<ONTORES2> onto2,
			File file
	) {

		if (file == null || !file.exists() || !file.isFile())
			throw new IllegalArgumentException(
					"File parameter is not a valid existing file.");

		// la date des correspondances sera toujours mise à la date de création du fichier
		DateTime date=new DateTime(file.lastModified());
		
		Model model = new LinkedHashModel();

		try {
			model=readModel(file,model);
		} catch (Exception e) {
			// Impossible, j'ai testé l'existence au dessus...
			e.printStackTrace();
			throw new IllegalArgumentException("Unable to read file. Right problem?");
		}

		Alignment<ONTORES1, ONTORES2> alignment = new Alignment<ONTORES1, ONTORES2>(
				onto1, onto2);

		for (Statement unTriplet: model) {

			if(
					unTriplet.getPredicate().equals(SKOS.EXACT_MATCH)
					||
					unTriplet.getPredicate().equals(SKOS.CLOSE_MATCH)
					||
					unTriplet.getPredicate().equals(SKOS.BROAD_MATCH)
					||
					unTriplet.getPredicate().equals(SKOS.NARROW_MATCH)
					||
					unTriplet.getPredicate().equals(SKOS.RELATED_MATCH)
					||
					unTriplet.getPredicate().equals(SKOS.MAPPING_RELATION)
					) {

				// Manage method
				String method = UNKNOWN_METHOD;
				// Manage validity
				VALIDITY valid = VALIDITY.VALID;
				// Manage creation date				
				//DateTime date = new DateTime();
				double score =1.0;

				// find the mapping type
				MAPPING_TYPE type = MAPPING_TYPE.getTypeFromSkosPredicate(unTriplet.getPredicate());
				
				if(type == null) {
					String errMsg = "Oops, should not happen, found an unexpected predicate "+unTriplet.getPredicate();
					ioEventManager.inputEvent(errMsg);
					continue;
				}

				Mapping<ONTORES1, ONTORES2> map;
				try {
					String uri1 = unTriplet.getSubject().stringValue();
					String uri2 = unTriplet.getObject().stringValue();
					// Prepare errMSg, maybe....
					String errMsg = "Loading alignment between " + uri1
							+ " and " + uri2
							+ " failed because this concepts does not exist: ";
					ONTORES1 cpt1 = onto1.getConceptFromURI(new URI(uri1));
					if (cpt1 == null) {
						ioEventManager.inputEvent(errMsg + uri1);
						continue;
					}
					ONTORES2 cpt2 = onto2.getConceptFromURI(new URI(uri2));
					if (cpt2 == null) {
						ioEventManager.inputEvent(errMsg + uri2);
						continue;
					}			

					map = new Mapping<ONTORES1, ONTORES2>(
							cpt1, cpt2, score, type, method, valid, date);

					alignment.addMap(map);
				} catch (URISyntaxException e) {
					ioEventManager.inputEvent("One of the value is not a valid uri");
					e.printStackTrace();
				}


			} else {
				System.out.println("Cannot understand SKOS mapping predicate "+unTriplet.getPredicate());
			}


		}

		return alignment;
	}

	public Model readModel(File file,Model model ){
		try {
			RDFFormat format = Rio.getParserFormatForFileName(file.getAbsolutePath()).orElse(RDFFormat.RDFXML);
			InputStream inputStream= new FileInputStream(file);
			try {
				model= Rio.parse(inputStream, file.getAbsolutePath(),format);
			} catch (RDFParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (UnsupportedRDFormatException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return model;
	}

	@Override
	public <ONTORES1, ONTORES2> void saveAlignment(
			Alignment<ONTORES1, ONTORES2> alignment, String pathToSave,
			VALIDITY validityWanted) throws IOException {


		ValueFactory factory = SimpleValueFactory.getInstance();
		Model model = new LinkedHashModel();
		model.setNamespace("skos", "http://www.w3.org/2004/02/skos/core#");
		model.setNamespace("", "http://www.w3.org/2004/02/skos/core#");

		final OntoContainer<ONTORES1> onto1 = alignment.getOnto1();
		final OntoContainer<ONTORES2> onto2 = alignment.getOnto2();

		Statement typeStatement =null;

		for (Mapping<ONTORES1, ONTORES2> mapping : alignment.getMapping()) {
			if(validityWanted != null && !mapping.getValidity().equals(validityWanted))
				continue;

			IRI mappingPredicate = mapping.getType().getSkosPropertyIri();
			if(mappingPredicate == null) {
				continue;
			}

			IRI res1= factory.createIRI(onto1.getURI(mapping.getFirstConcept()).toString());
			IRI res2= factory.createIRI(onto2.getURI(mapping.getSecondConcept()).toString());
			typeStatement=factory.createStatement(res1, mappingPredicate, res2);
			model.add(typeStatement);
		}

		FileOutputStream stream = new FileOutputStream(pathToSave);
		Rio.write(model, stream, RDFFormat.RDFXML);	
	}
}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.SortedMap;
import java.util.SortedSet;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import fr.onagui.alignment.Alignment;
import fr.onagui.alignment.Mapping;
import fr.onagui.alignment.Mapping.MAPPING_TYPE;
import fr.onagui.alignment.Mapping.VALIDITY;
import fr.onagui.alignment.NoMappingPossible;
import fr.onagui.alignment.OntoContainer;
import fr.onagui.alignment.io.CSVImpl;
import fr.onagui.alignment.io.EuzenatRDFImpl;
import fr.onagui.alignment.io.IOAlignment;
import fr.onagui.alignment.io.SkosImpl;




public class TestIO {

	public static Alignment<String, String> alignment;
	@SuppressWarnings("unchecked")
	public static OntoContainer<String> mockContainer = mock(OntoContainer.class);
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		alignment = new Alignment<>(mockContainer, mockContainer);
		when(mockContainer.getURI(anyString())).then(new Answer<URI>() {
			@Override
			public URI answer(InvocationOnMock invocation) throws Throwable {
				Object[] args = invocation.getArguments();
				return new URI("urn:"+args[0]);
			}
		});
		when(mockContainer.getConceptFromURI(any(URI.class))).then(new Answer<String> () {
			@Override
			public String answer(InvocationOnMock invocation) throws Throwable {
				Object[] args = invocation.getArguments();
				URI uri = (URI) args[0];
				return uri.toString().substring(4);
			}
		});
		when(mockContainer.getURI()).thenReturn(new URI("urn:onto"));
		when(mockContainer.getFormalism()).thenReturn("skos");
		alignment.addMap(new Mapping<String, String>("Cpt1", "Cpt2", 1.42, MAPPING_TYPE.OVERLAP, "Tests", VALIDITY.TO_CONFIRM));
		alignment.addImpossibleMappingFrom1(new NoMappingPossible<String>("Impossible1"));