/**
* 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.qparser.keyword.processors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
import org.apache.lucene.queryparser.flexible.core.config.QueryConfigHandler;
import org.apache.lucene.queryparser.flexible.core.nodes.AndQueryNode;
import org.apache.lucene.queryparser.flexible.core.nodes.BooleanQueryNode;
import org.apache.lucene.queryparser.flexible.core.nodes.GroupQueryNode;
import org.apache.lucene.queryparser.flexible.core.nodes.ModifierQueryNode;
import org.apache.lucene.queryparser.flexible.core.nodes.ModifierQueryNode.Modifier;
import org.apache.lucene.queryparser.flexible.core.nodes.OrQueryNode;
import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode;
import org.apache.lucene.queryparser.flexible.core.parser.SyntaxParser;
import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessor;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.Operator;
import org.apache.lucene.queryparser.flexible.standard.nodes.BooleanModifierNode;
import org.sindice.siren.qparser.keyword.nodes.ArrayQueryNode;
import org.sindice.siren.qparser.keyword.nodes.DatatypeQueryNode;
import org.sindice.siren.qparser.keyword.nodes.TopLevelQueryNode;
import org.sindice.siren.qparser.keyword.nodes.TwigQueryNode;
import org.sindice.siren.qparser.keyword.nodes.WildcardNodeQueryNode;
/**
* The {@link SyntaxParser} generates query node trees that consider the boolean
* operator precedence, but Lucene current syntax does not support boolean
* precedence, so this processor remove all the precedence and apply the
* equivalent modifier according to the boolean operation defined on an specific
* query node.
*
* <p>
*
* The original {@link org.apache.lucene.queryparser.flexible.standard.processors.GroupQueryNodeProcessor}
* was not supporting correctly nested groups. This processor interprets a
* {@link GroupQueryNode} and merges it with the node above it.
*/
public class GroupQueryNodeProcessor implements QueryNodeProcessor {
private ArrayList<QueryNode> queryNodeList;
private boolean latestNodeVerified;
private QueryConfigHandler queryConfig;
private Boolean usingAnd = false;
public GroupQueryNodeProcessor() {
// empty constructor
}
public QueryNode process(QueryNode queryTree) throws QueryNodeException {
if (!this.getQueryConfigHandler().has(ConfigurationKeys.DEFAULT_OPERATOR)) {
throw new IllegalArgumentException(
"DEFAULT_OPERATOR should be set on the QueryConfigHandler");
}
this.usingAnd = Operator.AND == this.getQueryConfigHandler()
.get(ConfigurationKeys.DEFAULT_OPERATOR) ? true : false;
if (queryTree instanceof GroupQueryNode) {
queryTree = ((GroupQueryNode) queryTree).getChild();
}
this.queryNodeList = new ArrayList<QueryNode>();
this.latestNodeVerified = false;
this.readTree(queryTree);
if (queryTree instanceof BooleanQueryNode) {
queryTree.set(this.queryNodeList);
return queryTree;
}
else {
return new BooleanQueryNode(this.queryNodeList);
}
}
/**
*/
private QueryNode applyModifier(final QueryNode node, final QueryNode parent) {
if (this.usingAnd) {
if (parent instanceof OrQueryNode) {
if (node instanceof ModifierQueryNode) {
final ModifierQueryNode modNode = (ModifierQueryNode) node;
if (modNode.getModifier() == Modifier.MOD_REQ) {
return modNode.getChild();
}
}
} else {
if (node instanceof ModifierQueryNode) {
final ModifierQueryNode modNode = (ModifierQueryNode) node;
if (modNode.getModifier() == Modifier.MOD_NONE) {
return new BooleanModifierNode(modNode.getChild(), Modifier.MOD_REQ);
}
} else {
return new BooleanModifierNode(node, Modifier.MOD_REQ);
}
}
} else {
if (parent instanceof AndQueryNode) {
if (node instanceof ModifierQueryNode) {
final ModifierQueryNode modNode = (ModifierQueryNode) node;
if (modNode.getModifier() == Modifier.MOD_NONE) {
return new BooleanModifierNode(modNode.getChild(), Modifier.MOD_REQ);
}
} else {
return new BooleanModifierNode(node, Modifier.MOD_REQ);
}
}
}
return node;
}
private void readTree(final QueryNode node) throws QueryNodeException {
if (node instanceof BooleanQueryNode) {
final List<QueryNode> children = node.getChildren();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size() - 1; i++) {
this.readTree(children.get(i));
}
this.processNode(node);
this.readTree(children.get(children.size() - 1));
} else {
this.processNode(node);
}
} else {
this.processNode(node);
}
}
private void processNode(final QueryNode node) throws QueryNodeException {
if (node instanceof AndQueryNode || node instanceof OrQueryNode) {
if (!this.latestNodeVerified && !this.queryNodeList.isEmpty()) {
this.queryNodeList.add(this.applyModifier(this.queryNodeList
.remove(this.queryNodeList.size() - 1), node));
this.latestNodeVerified = true;
}
}
else if (node instanceof GroupQueryNode) {
final ArrayList<QueryNode> actualQueryNodeList = this.queryNodeList;
actualQueryNodeList.add(this.applyModifier(this.process(node), node.getParent()));
this.queryNodeList = actualQueryNodeList;
this.latestNodeVerified = false;
}
else if (node instanceof TwigQueryNode) {
final ArrayList<QueryNode> actualQueryNodeList = this.queryNodeList;
final TwigQueryNode twigNode = (TwigQueryNode) node;
final QueryNode root = twigNode.getRoot();
final QueryNode child = twigNode.getChild();
if (!(root instanceof WildcardNodeQueryNode)) { // the root is not empty
twigNode.setRoot(this.process(root));
}
if (!(child instanceof WildcardNodeQueryNode)) { // the child is not empty
twigNode.setChild(this.process(child));
}
actualQueryNodeList.add(twigNode);
this.queryNodeList = actualQueryNodeList;
this.latestNodeVerified = false;
}
else if (node instanceof ArrayQueryNode) {
final ArrayList<QueryNode> actualQueryNodeList = this.queryNodeList;
final ArrayQueryNode arrayNode = (ArrayQueryNode) node;
final List<QueryNode> children = arrayNode.getChildren();
final List<QueryNode> newChildren = new ArrayList<QueryNode>();
for (final QueryNode child : children) {
// The unary modifier sets the occurrence of this value in the TwigQuery
if (!(child instanceof ModifierQueryNode)) {
newChildren.add(this.process(child));
} else {
newChildren.add(child);
}
}
arrayNode.set(newChildren);
actualQueryNodeList.add(arrayNode);
this.queryNodeList = actualQueryNodeList;
this.latestNodeVerified = false;
}
else if (node instanceof TopLevelQueryNode) {
final ArrayList<QueryNode> actualQueryNodeList = this.queryNodeList;
final TopLevelQueryNode topNode = (TopLevelQueryNode) node;
final QueryNode child = topNode.getChildren().get(0);
topNode.set(Arrays.asList(this.process(child)));
actualQueryNodeList.add(topNode);
this.queryNodeList = actualQueryNodeList;
this.latestNodeVerified = false;
}
else if (node instanceof DatatypeQueryNode) {
final ArrayList<QueryNode> actualQueryNodeList = this.queryNodeList;
final DatatypeQueryNode dtNode = (DatatypeQueryNode) node;
final QueryNode child = dtNode.getChild();
dtNode.set(Arrays.asList(this.applyModifier(this.process(child), node.getParent())));
actualQueryNodeList.add(dtNode);
this.queryNodeList = actualQueryNodeList;
this.latestNodeVerified = false;
}
else if (!(node instanceof BooleanQueryNode)) {
this.queryNodeList.add(this.applyModifier(node, node.getParent()));
this.latestNodeVerified = false;
}
}
public QueryConfigHandler getQueryConfigHandler() {
return this.queryConfig;
}
public void setQueryConfigHandler(final QueryConfigHandler queryConfigHandler) {
this.queryConfig = queryConfigHandler;
}
}