blob: d8d69a31791653bf51d2b21f6639c5c0a007bc3b [file] [log] [blame]
using Lucene.Net.QueryParsers.Flexible.Core.Messages;
using Lucene.Net.QueryParsers.Flexible.Core.Nodes;
using Lucene.Net.QueryParsers.Flexible.Messages;
using Lucene.Net.QueryParsers.Flexible.Standard.Parser;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Lucene.Net.QueryParsers.Flexible.Core.Builders
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/// <summary>
/// This class should be used when there is a builder for each type of node.
///
/// <para>
/// The type of node may be defined in 2 different ways: - by the field name,
/// when the node implements the <see cref="IFieldableNode"/> interface - by its class,
/// it keeps checking the class and all the interfaces and classes this class
/// implements/extends until it finds a builder for that class/interface
/// </para>
/// <para>
/// This class always check if there is a builder for the field name before it
/// checks for the node class. So, field name builders have precedence over class
/// builders.
/// </para>
/// <para>
/// When a builder is found for a node, it's called and the node is passed to the
/// builder. If the returned built object is not <c>null</c>, it's tagged
/// on the node using the tag <see cref="QueryTreeBuilder.QUERY_TREE_BUILDER_TAGID"/>.
/// </para>
/// <para>
/// The children are usually built before the parent node. However, if a builder
/// associated to a node is an instance of <see cref="QueryTreeBuilder{TQuery}"/>, the node is
/// delegated to this builder and it's responsible to build the node and its
/// children.
/// </para>
/// <seealso cref="IQueryBuilder{TQuery}"/>
/// </summary>
public class QueryTreeBuilder<TQuery> : QueryTreeBuilder, IQueryBuilder<TQuery>
{
private IDictionary<Type, IQueryBuilder<TQuery>> queryNodeBuilders;
private IDictionary<string, IQueryBuilder<TQuery>> fieldNameBuilders;
/// <summary>
/// <see cref="QueryTreeBuilder{TQuery}"/> constructor.
/// </summary>
public QueryTreeBuilder()
{
// empty constructor
}
/// <summary>
/// Associates a field name with a builder.
/// </summary>
/// <param name="fieldName">the field name</param>
/// <param name="builder">the builder to be associated</param>
public virtual void SetBuilder(string fieldName, IQueryBuilder<TQuery> builder)
{
if (this.fieldNameBuilders == null)
{
this.fieldNameBuilders = new Dictionary<string, IQueryBuilder<TQuery>>();
}
this.fieldNameBuilders[fieldName] = builder;
}
/// <summary>
/// Associates a <see cref="Type">class</see> (that implements <see cref="IQueryNode"/>) with a builder
/// </summary>
/// <param name="queryNodeClass">The type (a class that implements <see cref="IQueryNode"/>)</param>
/// <param name="builder">the builder to be associated</param>
public virtual void SetBuilder(Type queryNodeClass,
IQueryBuilder<TQuery> builder)
{
if (this.queryNodeBuilders == null)
{
this.queryNodeBuilders = new Dictionary<Type, IQueryBuilder<TQuery>>();
}
this.queryNodeBuilders[queryNodeClass] = builder;
}
private void Process(IQueryNode node)
{
if (node != null)
{
IQueryBuilder<TQuery> builder = GetBuilder(node);
if (!(builder is QueryTreeBuilder<TQuery>))
{
IList<IQueryNode> children = node.GetChildren();
if (children != null)
{
foreach (IQueryNode child in children)
{
Process(child);
}
}
}
ProcessNode(node, builder);
}
}
private IQueryBuilder<TQuery> GetBuilder(IQueryNode node)
{
IQueryBuilder<TQuery> builder = null;
if (this.fieldNameBuilders != null && node is IFieldableNode fieldableNode)
{
string field = fieldableNode.Field;
this.fieldNameBuilders.TryGetValue(field, out builder);
}
if (builder == null && this.queryNodeBuilders != null)
{
Type clazz = node.GetType();
do
{
builder = GetQueryBuilder(clazz);
if (builder == null)
{
Type[] classes = clazz.GetInterfaces();
foreach (Type actualClass in classes)
{
builder = GetQueryBuilder(actualClass);
if (builder != null)
{
break;
}
}
}
} while (builder == null && (clazz = clazz.BaseType) != null);
}
return builder;
}
private static void ProcessNode(IQueryNode node, IQueryBuilder<TQuery> builder) // LUCENENET: CA1822: Mark members as static
{
if (builder == null)
{
throw new QueryNodeException(new Message(
QueryParserMessages.LUCENE_QUERY_CONVERSION_ERROR, node
.ToQueryString(new EscapeQuerySyntax()), node.GetType()
.Name));
}
object obj = builder.Build(node);
if (obj != null)
{
node.SetTag(QUERY_TREE_BUILDER_TAGID, obj);
}
}
private IQueryBuilder<TQuery> GetQueryBuilder(Type clazz)
{
if (typeof(IQueryNode).IsAssignableFrom(clazz))
{
this.queryNodeBuilders.TryGetValue(clazz, out IQueryBuilder<TQuery> result);
return result;
}
return null;
}
/// <summary>
/// Builds some kind of object from a query tree. Each node in the query tree
/// is built using an specific builder associated to it.
/// </summary>
/// <param name="queryNode">the query tree root node</param>
/// <returns>the built object</returns>
/// <exception cref="QueryNodeException">if some node builder throws a
/// <see cref="QueryNodeException"/> or if there is a node which had no
/// builder associated to it</exception>
public virtual TQuery Build(IQueryNode queryNode)
{
Process(queryNode);
return (TQuery)queryNode.GetTag(QUERY_TREE_BUILDER_TAGID);
}
}
/// <summary>
/// LUCENENET specific class for accessing static members of <see cref="QueryTreeBuilder{TQuery}"/>
/// without referencing its generic closing type.
/// </summary>
public abstract class QueryTreeBuilder
{
/// <summary>
/// This tag is used to tag the nodes in a query tree with the built objects
/// produced from their own associated builder.
/// </summary>
public static readonly string QUERY_TREE_BUILDER_TAGID = typeof(QueryTreeBuilder).Name;
}
}