/** * Copyright 2014 National University of Ireland, Galway. * * This file is part of the SIREn project. Project and contact information: * * https://github.com/rdelbru/SIREn * * 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. */ package org.sindice.siren.search.node; import java.util.Iterator; import java.util.TreeMap; import org.apache.lucene.index.DocsAndPositionsEnum; import org.apache.lucene.search.Query; import org.sindice.siren.index.ConstrainedNodesEnum; import org.sindice.siren.index.DocsNodesAndPositionsEnum; import org.sindice.siren.index.IntervalConstrainedNodesEnum; import org.sindice.siren.index.LevelConstrainedNodesEnum; import org.sindice.siren.index.SingleIntervalConstrainedNodesEnum; import org.sindice.siren.index.SirenDocsEnum; /** * Abstract class for the SIREn's node queries * * <p> * * This class provides an interface to manage node constraints. When a * {@link DocsNodesAndPositionsEnum} is requested using the method * {@link #getDocsNodesAndPositionsEnum(DocsAndPositionsEnum)}, it traverses * all the {@link NodeQuery} ancestor of the current {@link NodeQuery} and * creates a {@link ConstraintStack}. Given the constraint stack, the * appropriate {@link ConstrainedNodesEnum} is created and returned. */ public abstract class NodeQuery extends Query { /** * The node level constraint. * <p> * Set to sentinel value -1 by default. */ protected int levelConstraint = -1; /** * Set a constraint on the node's level * <p> * Given that the root of the tree (level 0) is the document id, the node * level constraint ranges from 1 to <code>Integer.MAX_VALUE</code>. A node * level constraint of 0 will always return false. */ public void setLevelConstraint(final int levelConstraint) { this.levelConstraint = levelConstraint; } public int getLevelConstraint() { return levelConstraint; } /** * The lower and upper bound of the interval constraint over the node indexes. * <p> * Set to sentinel value -1 by default. */ protected int lowerBound = -1, upperBound = -1; /** * Set an index interval constraint for a node. These constraints are * inclusives. */ public void setNodeConstraint(final int lowerBound, final int upperBound) { this.lowerBound = lowerBound; this.upperBound = upperBound; } /** * Set the node index constraint. */ public void setNodeConstraint(final int index) { this.setNodeConstraint(index, index); } public int[] getNodeConstraint() { return new int[] { lowerBound, upperBound }; } class ConstraintStack { final TreeMap<Integer, Integer[]> stack = new TreeMap<Integer, Integer[]>(); protected void add(final int level, final int lowerBound, final int upperBound) { stack.put(level, new Integer[] { lowerBound, upperBound }); } protected int size() { return stack.size(); } protected int[] getLevelIndex() { final int[] levels = new int[stack.size()]; final Iterator<Integer> it = stack.keySet().iterator(); for (int i = 0; i < stack.size(); i++) { levels[i] = it.next(); } return levels; } protected int[][] getConstraints() { final int[][] constraints = new int[stack.size()][2]; final Iterator<Integer[]> it = stack.values().iterator(); for (int i = 0; i < stack.size(); i++) { final Integer[] constraint = it.next(); constraints[i] = new int[2]; constraints[i][0] = constraint[0]; constraints[i][1] = constraint[1]; } return constraints; } } /** * The pointer to direct node query ancestor */ protected NodeQuery ancestor; /** * Expert: Add a pointer to the node query ancestor * <p> * The pointer to node query ancestor is used to retrieve node constraints from * ancestors. */ protected void setAncestorPointer(final NodeQuery ancestor) { this.ancestor = ancestor; } /** * Provides a {@link DocsNodesAndPositionsEnum} given a * {@link DocsAndPositionsEnum}. If a set of constraints is applied, it * automatically wraps the {@link DocsNodesAndPositionsEnum} into a * {@link ConstrainedNodesEnum}. */ protected DocsNodesAndPositionsEnum getDocsNodesAndPositionsEnum(final DocsAndPositionsEnum docsEnum) { // Map Lucene's docs enum to a SIREn's docs, nodes and positions enum final DocsNodesAndPositionsEnum sirenDocsEnum = SirenDocsEnum.map(docsEnum); // Retrieve constraints starting from the direct ancestor final ConstraintStack stack = new ConstraintStack(); this.retrieveConstraint(this.ancestor, stack); // if at least one constraint has been found among the ancestors if (stack.size() > 0) { // add the interval constraint of the current node if (lowerBound != -1 && upperBound != -1) { stack.add(levelConstraint, lowerBound, upperBound); } return new IntervalConstrainedNodesEnum(sirenDocsEnum, levelConstraint, stack.getLevelIndex(), stack.getConstraints()); } // if an interval constraint has been set for the current node else if (lowerBound != -1 && upperBound != -1) { // use the interval constraint of the current node return new SingleIntervalConstrainedNodesEnum(sirenDocsEnum, levelConstraint, new int[] { lowerBound, upperBound }); } // if only a level constraint has been set for the current node else if (levelConstraint != -1) { return new LevelConstrainedNodesEnum(sirenDocsEnum, levelConstraint); } else { return sirenDocsEnum; } } private void retrieveConstraint(final NodeQuery query, final ConstraintStack stack) { if (query == null) { return; } // add a constraint only if lower and upper bounds are defined if (query.lowerBound != -1 && query.upperBound != -1) { stack.add(query.levelConstraint, query.lowerBound, query.upperBound); } // recursively traverse the ancestors this.retrieveConstraint(query.ancestor, stack); } }