/* Copyright (2007-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/>. */ package no.sesat.search.query.finder; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import no.sesat.search.query.Clause; import no.sesat.search.query.BinaryClause; import no.sesat.search.query.LeafClause; import no.sesat.search.query.UnaryClause; import no.sesat.commons.visitor.AbstractReflectionVisitor; import no.sesat.search.query.token.TokenPredicate; import org.apache.log4j.Logger; /** Visitor used to find a clause's parents. * Clauses' do not keep references to their parents as they are immutable and can thus be reused within different trees. * * * @version $Id$ */ public final class ParentFinder extends AbstractReflectionVisitor implements Serializable { private boolean searching = false; private boolean singleMode = false; private List<UnaryClause> parents = new ArrayList<UnaryClause>(); private Clause child; private final Map<Clause, Map<Clause, List<UnaryClause>>> cache = new HashMap<Clause, Map<Clause, List<UnaryClause>>>(); private static final Logger LOG = Logger.getLogger(ParentFinder.class); private static final String ERR_CANNOT_CALL_VISIT_DIRECTLY = "visit(object) can't be called directly on this visitor!"; private static final String ERR_CHILD_NOT_IN_HEIRARCHY = "The child is not part of this clause family!"; private final List<Clause> visitStack = new ArrayList<Clause>(); /** * * @param parents * @param token * @return */ public static boolean insideOf(final List<UnaryClause> parents, final TokenPredicate token){ boolean inside = false; for(UnaryClause oc : parents){ inside |= oc.getKnownPredicates().contains(token); } return inside; } /** Returns all parents, grandparents, great-grandparents, etc. * * @param root * @param clause * @return */ public synchronized List<UnaryClause> getAncestors(final Clause root, final Clause clause){ final List<UnaryClause> parents = new ArrayList<UnaryClause>(); for(UnaryClause oc : getParents(root, clause)){ parents.addAll(getAncestors(root, oc)); parents.add(oc); } return parents; } /** Returns all direct parents. * * @param root * @param child * @return */ public synchronized List<UnaryClause> getParents(final Clause root, final Clause child) { findParentsImpl(root, child); return Collections.unmodifiableList(new ArrayList<UnaryClause>( parents )); } /** Finds the first found direct parent. * * @param root * @param child * @return */ public synchronized UnaryClause getParent(final Clause root, final Clause child) { singleMode = true; findParentsImpl(root, child); singleMode = false; if (parents.size() == 0) { throw new IllegalArgumentException(ERR_CHILD_NOT_IN_HEIRARCHY); } return parents.get(0); } private List<UnaryClause> findInCache(final Clause root){ Map<Clause, List<UnaryClause>> innerCache = cache.get(root); if (innerCache == null){ innerCache = new HashMap<Clause, List<UnaryClause>>(); cache.put(root, innerCache); } return innerCache.get(child); } private void updateCache(final Clause root){ Map<Clause, List<UnaryClause>> innerCache = cache.get(root); if (innerCache == null){ innerCache = new HashMap<Clause, List<UnaryClause>>(); cache.put(root, innerCache); } innerCache.put(child, new ArrayList<UnaryClause>(parents)); } private synchronized <T extends BinaryClause> void findParentsImpl(final Clause root, final Clause child) { this.child = child; if (searching || child == null) { throw new IllegalStateException(ERR_CANNOT_CALL_VISIT_DIRECTLY); } searching = true; parents.clear(); visitStack.clear(); addVisit(root); if(null == findInCache(root)){ visit(root); updateCache(root); }else{ parents.addAll(findInCache(root)); } searching = false; this.child = null; } /** * * @param clause */ protected void visitImpl(final UnaryClause clause) { if (!singleMode || parents.size() == 0) { if (clause.getFirstClause() == child){ parents.add(clause); } addVisit(clause.getFirstClause()); clause.getFirstClause().accept(this); removeVisit(clause.getFirstClause()); } } /** * * @param clause */ protected void visitImpl(final BinaryClause clause) { if (!singleMode || parents.size() == 0) { if (clause.getFirstClause() == child || clause.getSecondClause() == child) { parents.add(clause); } addVisit(clause.getFirstClause()); clause.getFirstClause().accept(this); removeVisit(clause.getFirstClause()); addVisit(clause.getSecondClause()); clause.getSecondClause().accept(this); removeVisit(clause.getSecondClause()); } } /** * * @param clause */ protected void visitImpl(final LeafClause clause) { // leaves can't be parents :-) } private void addVisit(final Clause clause){ if(visitStack.contains(clause)){ // !serious error! we've gotten into a recursive loop! See SEARCH-2235 final String msg = "!serious error! we've gotten into a recursive loop! See SEARCH-2235\n"; final StringBuilder builder = new StringBuilder(msg); builder.append("Were looking for child " + child + '\n'); for(Clause c: visitStack){ builder.append(c.toString() + '\n'); } builder.append(clause.toString() + '\n'); LOG.error(builder.toString()); throw new IllegalStateException(msg); } visitStack.add(clause); } private void removeVisit(final Clause clause){ visitStack.remove(clause); } }