content: // This file is part of AceWiki.
  // Copyright 2008-2013, AceWiki developers.
  // 
  // AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU
  // Lesser General Public License as published by the Free Software Foundation, either version 3 of
  // the License, or (at your option) any later version.
  // 
  // AceWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
  // even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  // Lesser General Public License for more details.
  // 
  // You should have received a copy of the GNU Lesser General Public License along with AceWiki. If
  // not, see http://www.gnu.org/licenses/.
  
  package ch.uzh.ifi.attempto.acewiki.core;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * This reasoner class wraps another reasoner and adds caching functionality.
   * 
   * @author Tobias Kuhn
   */
  public class CachingReasoner implements AceWikiReasoner {
  	
  	private AceWikiReasoner wrappedReasoner;
  	private Ontology ontology;
  	
  	private Map<String, CachedAnswer> answerCache = new HashMap<String, CachedAnswer>();
  	private Map<Long, CachedConcepts> conCache = new HashMap<Long, CachedConcepts>();
  	private Map<Long, CachedIndividuals> indCache = new HashMap<Long, CachedIndividuals>();
  	private Map<Long, CachedConcepts> supConCache = new HashMap<Long, CachedConcepts>();
  	private Map<Long, CachedConcepts> subConCache = new HashMap<Long, CachedConcepts>();
  	
  	/**
  	 * Creates a new caching reasoner for the given reasoner to be wrapped.
  	 * 
  	 * @param wrappedReasoner The reasoner to be wrapped.
  	 */
  	CachingReasoner(AceWikiReasoner wrappedReasoner) {
  		this.wrappedReasoner = wrappedReasoner;
  	}
  
  	public synchronized void init(Ontology ontology) {
  		this.ontology = ontology;
  	}
  	
  	/**
  	 * Returns the wrapped reasoner.
  	 * 
  	 * @return The wrapped reasoner.
  	 */
  	public AceWikiReasoner getWrappedReasoner() {
  		return wrappedReasoner;
  	}
  	
  	private long getState() {
  		return ontology.getStateID();
  	}
  	
  	/**
  	 * Returns whether the there is an up-to-date cached answer for the given question.
  	 * 
  	 * @param question The question.
  	 * @return true if there is an up-to-date cached answer.
  	 */
  	public synchronized boolean isCachedAnswerUpToDate(Question question) {
  		CachedAnswer a = answerCache.get(question.serialize());
  		if (a != null) {
  			return a.state == getState();
  		} else {
  			return false;
  		}
  	}
  	
  	/**
  	 * Returns the cached answer for the given question, or null if no cached answer exists.
  	 * The answer might not be up-to-date.
  	 * 
  	 * @param question The question.
  	 * @return The cached answer.
  	 */
  	public synchronized List<AnswerElement> getCachedAnswer(Question question) {
  		CachedAnswer a = answerCache.get(question.serialize());
  		if (a != null) {
  			return new ArrayList<AnswerElement>(a.list);
  		} else {
  			return null;
  		}
  	}
  	
  	/**
  	 * Returns the answer for the given question. The cache is used if it is up-to-date.
  	 */
  	public synchronized List<AnswerElement> getAnswer(Question question) {
  		CachedAnswer a = answerCache.get(question.serialize());
  		if (a != null && a.state == getState()) {
  			return new ArrayList<AnswerElement>(a.list);
  		} else {
  			a = new CachedAnswer();
  			a.list = wrappedReasoner.getAnswer(question);
  			a.state = getState();
  			answerCache.put(question.serialize(), a);
  			return a.list;
  		}
  	}
  
  	/**
  	 * Returns true if the concepts of the given individual are cached and up-to-date and thus
  	 * do not have to be recalculated.
  	 * 
  	 * @param ind The individual.
  	 * @return true if the cached concepts are up-to-date.
  	 */
  	public synchronized boolean areCachedConceptsUpToDate(Individual ind) {
  		CachedConcepts c = conCache.get(ind.getId());
  		if (c != null) {
  			return c.state == getState();
  		} else {
  			return false;
  		}
  	}
  
  	/**
  	 * Returns the cached concepts for the given individual or null if there are no cached
  	 * concepts. The returned concepts might not be up-to-date.
  	 * 
  	 * @param ind The individual.
  	 * @return A list of the cached concepts for the given individual.
  	 */
  	public synchronized List<Concept> getCachedConcepts(Individual ind) {
  		CachedConcepts c = conCache.get(ind.getId());
  		if (c != null) {
  			return new ArrayList<Concept>(c.list);
  		} else {
  			return null;
  		}
  	}
  	
  	public synchronized List<Concept> getConcepts(Individual ind) {
  		CachedConcepts c = conCache.get(ind.getId());
  		if (c != null && c.state == getState()) {
  			return new ArrayList<Concept>(c.list);
  		} else {
  			c = new CachedConcepts();
  			c.list = wrappedReasoner.getConcepts(ind);
  			c.state = getState();
  			conCache.put(ind.getId(), c);
  			return c.list;
  		}
  	}
  
  	/**
  	 * Returns true if the individuals of the given concept are cached and up-to-date and thus
  	 * do not have to be recalculated.
  	 * 
  	 * @param concept The concept.
  	 * @return true if the cached individuals are up-to-date.
  	 */
  	public synchronized boolean areCachedIndividualsUpToDate(Concept concept) {
  		CachedIndividuals i = indCache.get(concept.getId());
  		if (i != null) {
  			return i.state == getState();
  		} else {
  			return false;
  		}
  	}
  
  	/**
  	 * Returns the cached individuals for the given concept or null if there are no cached
  	 * individuals. The returned individuals might not be up-to-date.
  	 * 
  	 * @param concept The concept.
  	 * @return A list of the cached individuals for the given concept.
  	 */
  	public synchronized List<Individual> getCachedIndividuals(Concept concept) {
  		CachedIndividuals i = indCache.get(concept.getId());
  		if (i != null) {
  			return new ArrayList<Individual>(i.list);
  		} else {
  			return null;
  		}
  	}
  	
  	public synchronized List<Individual> getIndividuals(Concept concept) {
  		CachedIndividuals i = indCache.get(concept.getId());
  		if (i != null && i.state == getState()) {
  			return new ArrayList<Individual>(i.list);
  		} else {
  			i = new CachedIndividuals();
  			i.list = wrappedReasoner.getIndividuals(concept);
  			i.state = getState();
  			indCache.put(concept.getId(), i);
  			return i.list;
  		}
  	}
  
  	/**
  	 * Returns true if the suber-concepts of the given concept are cached and up-to-date and thus
  	 * do not have to be recalculated.
  	 * 
  	 * @param concept The concept.
  	 * @return true if the cached super-concepts are up-to-date.
  	 */
  	public synchronized boolean areCachedSuperConceptsUpToDate(Concept concept) {
  		CachedConcepts c = supConCache.get(concept.getId());
  		if (c != null) {
  			return c.state == getState();
  		} else {
  			return false;
  		}
  	}
  
  	/**
  	 * Returns the cached super-concepts for the given concept or null if there are no cached
  	 * super-concepts. The returned super-concepts might not be up-to-date.
  	 * 
  	 * @param concept The concept.
  	 * @return A list of the cached super-concepts for the given concept.
  	 */
  	public synchronized List<Concept> getCachedSuperConcepts(Concept concept) {
  		CachedConcepts c = supConCache.get(concept.getId());
  		if (c != null) {
  			return new ArrayList<Concept>(c.list);
  		} else {
  			return null;
  		}
  	}
  	
  	public synchronized List<Concept> getSuperConcepts(Concept concept) {
  		CachedConcepts c = supConCache.get(concept.getId());
  		if (c != null && c.state == getState()) {
  			return new ArrayList<Concept>(c.list);
  		} else {
  			c = new CachedConcepts();
  			c.list = wrappedReasoner.getSuperConcepts(concept);
  			c.state = getState();
  			supConCache.put(concept.getId(), c);
  			return c.list;
  		}
  	}
  
  	/**
  	 * Returns true if the sub-concepts of the given concept are cached and up-to-date and thus
  	 * do not have to be recalculated.
  	 * 
  	 * @param concept The concept.
  	 * @return true if the cached sub-concepts are up-to-date.
  	 */
  	public synchronized boolean areCachedSubConceptsUpToDate(Concept concept) {
  		CachedConcepts c = subConCache.get(concept.getId());
  		if (c != null) {
  			return c.state == getState();
  		} else {
  			return false;
  		}
  	}
  
  	/**
  	 * Returns the cached sub-concepts for the given concept or null if there are no cached
  	 * sub-concepts. The returned sub-concepts might not be up-to-date.
  	 * 
  	 * @param concept The concept.
  	 * @return A list of the cached sub-concepts for the given concept.
  	 */
  	public synchronized List<Concept> getCachedSubConcepts(Concept concept) {
  		CachedConcepts c = subConCache.get(concept.getId());
  		if (c != null) {
  			return new ArrayList<Concept>(c.list);
  		} else {
  			return null;
  		}
  	}
  	
  	public synchronized List<Concept> getSubConcepts(Concept concept) {
  		CachedConcepts c = subConCache.get(concept.getId());
  		if (c != null && c.state == getState()) {
  			return new ArrayList<Concept>(c.list);
  		} else {
  			c = new CachedConcepts();
  			c.list = wrappedReasoner.getSubConcepts(concept);
  			c.state = getState();
  			subConCache.put(concept.getId(), c);
  			return c.list;
  		}
  	}
  
  	/**
  	 * Returns the name of the reasoner.
  	 * 
  	 * @return The name of the reasoner.
  	 */
  	public String getReasonerName() {
  		return wrappedReasoner.getReasonerName();
  	}
  	
  	/**
  	 * Return the version of the reasoner.
  	 * 
  	 * @return The version of the reasoner.
  	 */
  	public String getReasonerVersion() {
  		return wrappedReasoner.getReasonerVersion();
  	}
  
  	/**
  	 * Return the type of the reasoner.
  	 * 
  	 * @return The reasoner type.
  	 */
  	public String getReasonerType() {
  		return wrappedReasoner.getReasonerType();
  	}
  	
  	public Map<String, String> getInfo() {
  		return wrappedReasoner.getInfo();
  	}
  	
  	/**
  	 * Loads the reasoner or reasoner interface.
  	 */
  	public synchronized void load() {
  		wrappedReasoner.load();
  	}
  	
  	public synchronized void loadElement(OntologyElement element) {
  		wrappedReasoner.loadElement(element);
  	}
  	
  	public synchronized void unloadElement(OntologyElement element) {
  		wrappedReasoner.unloadElement(element);
  	}
  	
  	public synchronized boolean isConsistent() {
  		return wrappedReasoner.isConsistent();
  	}
  	
  	public synchronized boolean isSatisfiable(Concept concept) {
  		return wrappedReasoner.isSatisfiable(concept);
  	}
  	
  	public synchronized void loadSentence(Sentence sentence) {
  		wrappedReasoner.loadSentence(sentence);
  	}
  	
  	public synchronized void unloadSentence(Sentence sentence) {
  		wrappedReasoner.unloadSentence(sentence);
  	}
  	
  	public synchronized void flushElements() {
  		wrappedReasoner.flushElements();
  	}
  	
  	
  	// Small internal classes for cached objects:
  	
  	private static class CachedAnswer {
  		long state = -1;
  		List<AnswerElement> list;
  	}
  	
  	private static class CachedIndividuals {
  		long state = -1;
  		List<Individual> list;
  	}
  	
  	private static class CachedConcepts {
  		long state = -1;
  		List<Concept> list;
  	}
  
  }
  