/* Copyright (2005-2012) Schibsted ASA
* This file is part of Possom.
*
* Possom 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.
*
* Possom 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 Possom. If not, see <http://www.gnu.org/licenses/>.
*
* AbstractClause.java
*
* Created on 11 January 2006, 14:17
*
*/
package no.sesat.search.query.parser;
import java.util.Collections;
import java.util.Set;
import no.sesat.commons.ref.ReferenceMap;
import no.sesat.search.query.Clause;
import no.sesat.commons.visitor.Visitor;
import no.sesat.search.query.token.DeadTokenEvaluationEngineImpl.DeadEvaluationRuntimeException;
import no.sesat.search.query.token.TokenEvaluator;
import no.sesat.search.query.token.TokenEvaluationEngine;
import no.sesat.search.query.token.TokenPredicate;
import no.sesat.search.query.token.EvaluationException;
import no.sesat.search.query.token.EvaluationRuntimeException;
import no.sesat.search.query.token.TokenPredicateUtility;
import org.apache.log4j.Logger;
/** Basic implementation of the Clause interface.
* Provides basic implementation of the predicates lists, defaulting to empty lists.
* Also provides weak reference hashmaps to keep record and reuse the Clauses already in use in the JVM.
* <b>Objects of this class are immutable</b>
*
* @version $Id$
*
*/
public abstract class AbstractClause implements Clause {
private static final Logger LOG = Logger.getLogger(AbstractClause.class);
/**
* Error message when reflection cannot find the required constructor.
*/
protected static final String ERR_FAILED_FINDING_OR_USING_CONSTRUCTOR
= "Failed to find (or use) constructor with parameters (String, String, Set, Set) for class: ";
/**
* Error message when trying to use the incorrect constructor.
**/
protected static final String ERR_MUST_ALWAYS_USE_ARGED_CONSTRUCTOR
= "Illegal to call constructor without arguments!";
private static final String DEBUG_FOUND_PREDICATE_PREFIX = "Found (for \"";
private static final String DEBUG_FOUND_PREDICATE_KNOWN = "\") known ";
private static final String DEBUG_FOUND_PREDICATE_POSSIBLE = "\") possible ";
private static final String INFO_WEAK_CACHE_SIZE_1 ="WeakCache for ";
private static final String INFO_WEAK_CACHE_SIZE_2 =" at ";
private static final String DEBUG_REFERENCE_REUSED = "Re-using a weakReference. Cache size: ";
private static final String ERR_FAILED_TO_FIND_ALL_PREDICATES = "Failed to find all predicates."
+ " Marking token predicate stale >>";
private final String term;
private final Set<TokenPredicate> knownPredicates;
private final Set<TokenPredicate> possiblePredicates;
public static final ReferenceMap.Type DFAULT_REFERENCE_MAP_TYPE = ReferenceMap.Type.WEAK;
/**
* See if there is an identical and immutable Clause already in use in the JVM.
* @param <T>
* @param key the <B>unique</B> (for this AbstractClause subtype) key for the Clause we are looking for.
* @param weakCache the map containing the key to WeakReference (of the Clause) mappings.
* @return the AbstractClause in use already, matching the key. <B>May be <CODE>null</CODE></B>.
*/
protected static final <T extends AbstractClause> T findClauseInUse(
final String key,
final ReferenceMap<String,T> weakCache) {
T result = weakCache.get(key);
return result;
}
/**
* Note there is an identical and immutable Clause ready to use in the JVM.
* @param <T>
* @param key the <B>unique</B> (for this AbstractClause subtype) key
* for the Clause we are about to add to the mappings.
* @param clause the Clause we are about to add to the mappings.
* @param weakCache the map containing the key to WeakReference (of the Clause) mappings.
* @return If the weakCache contained an clause for the key, then this is returned. Otherwise
* the clasue entered as a parameter is returned.
*/
protected static final <T extends AbstractClause> T addClauseInUse(
final String key,
final T clause,
final ReferenceMap<String,T> weakCache) {
T inUse = weakCache.get(key);
if(null == inUse){
// clause is not in use
inUse = weakCache.put(key, clause);
if(null != inUse){
// weakCache.get(key) is only read volatile so it may be there was another clause already there
// and only the write volatile inside weakCache.put(key.calue) sees through this small window.
// restore original clause that was in use
weakCache.put(key, inUse);
}else{
inUse = clause;
}
}
return inUse;
}
/**
* Find the predicates that are applicable to the clause.
* (Only the clause's term is known and is kept in state inside the TokenEvaluationEngine).
* Add known predicates to <CODE>knownPredicates</CODE>.
* Add possible (requires further checking against the whole query heirarchy)
* predicates to <CODE>possiblePredicates</CODE>.
*
* @param engine the factory handing out evaluators against TokenPredicates.
* Also holds state information about the current term/clause we are finding predicates against.
* to the current clause we are finding predicates for.
* @return
*/
protected static final boolean findPredicates(final TokenEvaluationEngine engine) {
boolean success = true;
for (TokenPredicate token : TokenPredicateUtility.getTokenPredicates()) {
success &= findPredicate(engine, token, success);
success &= findPredicate(engine, token.exactPeer(), success);
}
return success;
}
private static final boolean findPredicate(
final TokenEvaluationEngine engine,
final TokenPredicate token,
final boolean pastSuccess){
boolean success = pastSuccess;
final Set<TokenPredicate> knownPredicates = engine.getState().getKnownPredicates();
final Set<TokenPredicate> possiblePredicates = engine.getState().getPossiblePredicates();
final String currTerm = engine.getState().getTerm();
// check it hasn't already been added
if(!(knownPredicates.contains(token) || possiblePredicates.contains(token))){
try{
if (token.evaluate(engine)) {
final TokenEvaluator evaluator = engine.getEvaluator(token);
if (evaluator.isQueryDependant(token)) {
possiblePredicates.add(token);
LOG.debug(DEBUG_FOUND_PREDICATE_PREFIX + currTerm + DEBUG_FOUND_PREDICATE_POSSIBLE + token);
} else {
knownPredicates.add(token);
LOG.debug(DEBUG_FOUND_PREDICATE_PREFIX + currTerm + DEBUG_FOUND_PREDICATE_KNOWN + token);
}
}
}catch(EvaluationException ie){
if(success){
success = false;
LOG.error(ERR_FAILED_TO_FIND_ALL_PREDICATES + currTerm);
}
}catch(DeadEvaluationRuntimeException dere){
success |= false;
// don't log this as it is intentional evaluation failure
}catch(EvaluationRuntimeException ee){
if(success){
success = false;
LOG.error(ERR_FAILED_TO_FIND_ALL_PREDICATES + currTerm);
}
}
}
return success;
}
/**
* Create clause with the given term, known and possible predicates.
* @param term the term (query string) for this clause.
* @param knownPredicates the set of known predicates for this clause.
* @param possiblePredicates the set of possible predicates for this clause.
*/
protected AbstractClause(
final String term,
final Set<TokenPredicate> knownPredicates,
final Set<TokenPredicate> possiblePredicates) {
this.term = term;
this.knownPredicates = Collections.unmodifiableSet(knownPredicates);
this.possiblePredicates = Collections.unmodifiableSet(possiblePredicates);
}
/**
* Get the term for this Clause.
* Does not include any field values (eg "firstname:").
* @return the term for this clause.
*/
@Override
public String getTerm() {
return term;
}
/**
* Get the set of knownPredicates for this Clause.
* The set is unmodifiable.
* @return set of knownPredicates.
*/
@Override
public Set<TokenPredicate> getKnownPredicates() {
return knownPredicates;
}
/**
* Get the set of possiblePredicates for this Clause.
* The set is unmodifiable.
* @return set of possiblePredicates.
*/
@Override
public Set<TokenPredicate> getPossiblePredicates() {
return possiblePredicates;
}
@Override
public void accept(final Visitor visitor) {
visitor.visit(this);
}
@Override
public String toString() {
return getClass().getSimpleName() + '[' + getTerm() + ']';
}
/** Provide a replicatable hashCode so the same segment inside the ConcurrentHashMap
* is used for any accidently duplicate created clauses.
* The ConcurrentHashMap is used in this package's implementation of the flyweight pattern.
*
* It is intended for equals(..) to use instance reference equality, ie Object.equals(..).
*
* {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public int hashCode() {
return ((getClass().hashCode() * 37) + term.hashCode()) + 17;
}
}