content: /******************************************************************************
  * Copyright 2013-2016 LASIGE                                                  *
  *                                                                             *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may     *
  * not use this file except in compliance with the License. You may obtain a   *
  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0           *
  *                                                                             *
  * Unless required by applicable law or agreed to in writing, software         *
  * distributed under the License is distributed on an "AS IS" BASIS,           *
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.    *
  * See the License for the specific language governing permissions and         *
  * limitations under the License.                                              *
  *                                                                             *
  *******************************************************************************
  * A matching algorithm that derives a thesaurus from the Lexicon of each of   *
  * input ontologies, then uses that thesaurus to create a temporary extended   *
  * Lexicon which is used to match the ontologies.                              *
  *                                                                             *
  * @authors Catia Pesquita, Daniel Faria                                       *
  ******************************************************************************/
  package aml.match;
  
  import java.util.HashSet;
  import java.util.Set;
  import java.util.Vector;
  
  import aml.AML;
  import aml.ontology.Lexicon;
  import aml.settings.EntityType;
  import aml.settings.InstanceMatchingCategory;
  import aml.settings.LexicalType;
  import aml.util.StringParser;
  import aml.util.Table2List;
  
  public class ThesaurusMatcher implements PrimaryMatcher
  {
  	
  //Attributes
  	
  	private static final String DESCRIPTION = "Matches entities that have one or more exact\n" +
  											  "String matches between their Lexicon entries\n" +
  											  "after extension using an internally generated\n" +
  											  "Thesaurus";
  	private static final String NAME = "WordNet Matcher";
  	private static final EntityType[] SUPPORT = {EntityType.CLASS,EntityType.INDIVIDUAL,EntityType.DATA,EntityType.OBJECT};
  	//The type of lexical entry generated by this LexiconExtender
  	private final LexicalType TYPE = LexicalType.INTERNAL_SYNONYM;
  	//The source of this LexiconExtender
  	private final String SOURCE = "Thesaurus";
  	//The confidence score of WordNet
  	private final double CONFIDENCE = 0.9;
  	//The Thesaurus of synonym words (String,String)
  	public Table2List<String,String> thesaurus;
  	
  //Constructors
  	
  	public ThesaurusMatcher(){}
  
  //Public Methods
  	
  	@Override
  	public String getDescription()
  	{
  		return DESCRIPTION;
  	}
  
  	@Override
  	public String getName()
  	{
  		return NAME;
  	}
  
  	@Override
  	public EntityType[] getSupportedEntityTypes()
  	{
  		return SUPPORT;
  	}
  	
  	@Override
  	public Alignment match(EntityType e, double thresh) throws UnsupportedEntityTypeException
  	{
  		checkEntityType(e);
  		System.out.println("Running Thesaurus Matcher");
  		long time = System.currentTimeMillis()/1000;
  		AML aml = AML.getInstance();
  		Lexicon source = new Lexicon(aml.getSource().getLexicon());
  		Lexicon target = new Lexicon(aml.getTarget().getLexicon());
  		extendLexicon(source,e,thresh);
  		extendLexicon(target,e,thresh);
  		Alignment a = match(source,target,e,thresh);
  		time = System.currentTimeMillis()/1000 - time;
  		System.out.println("Finished in " + time + " seconds");
  		return a;
  	}
  	
  //Private Methods
  	
  	//Adds entries to the Thesaurus based on a set of names for a concept
  	private void addSynonymTerms(Set<String> names)
  	{
  		//Compare the set of names pairwise
  		String[] namesarray = names.toArray(new String[0]);
  		for(int i = 0; i < namesarray.length; i++)
  			for(int j = i; j < namesarray.length; j++)
  				compareSynonyms(namesarray[i], namesarray[j]);
  	}
  	
  	//Checks if a given EntityType is supported by this matcher
  	private void checkEntityType(EntityType e) throws UnsupportedEntityTypeException
  	{
  		boolean check = false;
  		for(EntityType t : SUPPORT)
  		{
  			if(t.equals(e))
  			{
  				check = true;
  				break;
  			}
  		}
  		if(!check)
  			throw new UnsupportedEntityTypeException(e.toString());
  	}
  
  	//Extracts subconcept synonyms from two given synonyms
  	private void compareSynonyms(String synonym1, String synonym2)
  	{
  		//Step 0. Check if either synonym is a formula, and if so, return
  		if(StringParser.isFormula(synonym1) || StringParser.isFormula(synonym2))
  			return;
  			
  		//Step 1. Setup
  		//Split the synonyms
  		String[] words1 = synonym1.split(" ");
  		String[] words2 = synonym2.split(" ");
  		//Check if they have the same number of words
  		if(words1.length != words2.length)
  			return;
  
  		//Step 2. Comparison
  		//Check that only one word is different between the two synonyms
  		//Trivial case - one-word names
  		if(words1.length == 1)
  		{
  			thesaurus.add(synonym1, synonym2);
  			thesaurus.add(synonym2, synonym1);
  			return;
  		}
  		//Multi-word names
  		int index = -1;
  		for(int i = 0; i < words1.length; i++)
  		{
  			if(!words1[i].equals(words2[i]))
  			{
  				//More than one mismatch
  				if(index != -1)
  					return;
  				else
  					index = i;
  			}
  		}
  		if(index != -1 && words1[index].length() > 2 && words2[index].length() > 2)
  		{
  			thesaurus.add(words1[index], words2[index]);
  			thesaurus.add(words2[index], words1[index]);
  		}
  	}
  	
  	//Extends a Lexicon with Thesaurus synonyms
  	private void extendLexicon(Lexicon lex, EntityType e, double thresh)
  	{
  		//Step 1 - Build the Thesaurus
  		thesaurus = new Table2List<String,String>();
  		//Get the entities from the Lexicon
  		Set<Integer> ents = lex.getEntities(e);
  		//And compute the synonyms for each entity
  		for(Integer i : ents)
  			addSynonymTerms(lex.getInternalNames(i));
  
  		//Step 2 - Extend the Lexicon
  		Set<String> names = new HashSet<String>(lex.getNames(e));
  		//For each name in the Lexicon
  		for(String n: names)
  		{
  			//If it is a formula, skip to the next name
  			if(StringParser.isFormula(n))
  				continue;
  			//Otherwise, for each entry in the Thesaurus
  			for(String s: thesaurus.keySet())
  			{
  				//If the entry is not contained in the name, skip to next entry
  				if(!n.contains(s))
  					continue;
  				//Otherwise, get the Thesaurus synonyms for that entry
  				Vector<String> thesEntries = thesaurus.get(s);
  				//For each Thesaurus synonym, create a new synonym in the Lexicon
  				Set<Integer> terms = lex.getInternalEntities(EntityType.CLASS,n);
  				for(String t: thesEntries)
  				{
  					String newName = n.replace(s,t);
  					for(Integer i: terms)
  					{
  						double weight = lex.getWeight(n,i) * CONFIDENCE;
  						if(weight >= thresh && !lex.contains(i, newName))
  							lex.add(i, newName, lex.getLanguages(n,i).iterator().next(), TYPE, SOURCE, weight);
  					}
  				}
  			}
  		}
  	}
  	
  	//Matches two Lexicons
  	private Alignment match(Lexicon source, Lexicon target, EntityType e, double thresh)
  	{
  		AML aml = AML.getInstance();
  		Alignment maps = new Alignment();
  		Set<String> names = source.getNames(e);
  		for(String s : names)
  		{
  			//Get all term indexes for the name in both ontologies
  			Set<Integer> sIndexes = source.getEntities(e,s);
  			Set<Integer> tIndexes = target.getEntities(e,s);
  			if(tIndexes == null)
  				continue;
  			//Otherwise, match all indexes
  			for(Integer i : sIndexes)
  			{
  				if(e.equals(EntityType.INDIVIDUAL) && !aml.isToMatchSource(i))
  					continue;
  				//Get the weight of the name for the term in the smaller lexicon
  				double weight = source.getCorrectedWeight(s, i);
  				for(Integer j : tIndexes)
  				{
  					if(e.equals(EntityType.INDIVIDUAL) && (!aml.isToMatchTarget(j) ||
  							(aml.getInstanceMatchingCategory().equals(InstanceMatchingCategory.SAME_CLASSES) &&
  							!aml.getRelationshipMap().shareClass(i,j))))
  						continue;
  					//Get the weight of the name for the term in the larger lexicon
  					double similarity = target.getCorrectedWeight(s, j);
  					//Then compute the similarity, by multiplying the two weights
  					similarity *= weight;
  					//If the similarity is above threshold add the mapping
  					if(similarity >= thresh)
  						maps.add(new Mapping(i, j, similarity));
  				}
  			}
  		}
  		return maps;	
  	}
  }