All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hp.autonomy.aci.content.fieldtext.FieldTextBuilder Maven / Gradle / Ivy

Go to download

API for representing aspects of IDOL's Content server, including FieldText and document References

The newest version!
/*
 * Copyright 2009-2017 Open Text.
 *
 * Licensed under the MIT License (the "License"); you may not use this file
 * except in compliance with the License.
 *
 * The only warranties for products and services of Open Text and its affiliates
 * and licensors ("Open Text") are as may be set forth in the express warranty
 * statements accompanying such products and services. Nothing herein should be
 * construed as constituting an additional warranty. Open Text shall not be
 * liable for technical or editorial errors or omissions contained herein. The
 * information contained herein is subject to change without notice.
 */

package com.hp.autonomy.aci.content.fieldtext;

import org.apache.commons.lang.Validate;

import static com.hp.autonomy.aci.content.fieldtext.FieldTexts.MATCHNOTHING;

/**
 * 

{@code FieldTextBuilder} allows for the building of complex fieldtext expressions from other fieldtext expressions * or specifiers. e.g.: *

*

 *    FieldTextBuilder builder = new FieldTextBuilder(new MATCH("MYFIELD", "VALUE"));
 *    builder.AND(new EQUAL("NUMERIC_FIELD", 6));
*

* evaluates to: *

*

    MATCH{VALUE}:MYFIELD+AND+EQUAL{6}:NUMERIC_FIELD
* * Note that calls to {@code AND}, {@code OR}, {@code XOR} and {@code NOT} return the same builder on which they were * called so the return value is usually discarded. * *

{@code FieldTextBuilder} manages boolean operators to ensure that there are no leading or trailing operators. * Further, it will omit parentheses where possible. * *

This class is {@code final} to ensure that the assumptions made in the optimizations for combining * {@code FieldTextBuilder}s cannot be violated. * *

This class makes no attempt at internal synchronization and is not safe to be used by multiple threads without * external synchronization. * *

The lack of an empty constructor is not an oversight, it caused some confusion in earlier versions of the class * and has been removed. The problem occurred in the treatment of the OR operator, which is logically expected to * increase the size of the results set. However, an empty fieldtext matches all documents so appending more fieldtext * via OR should have no effect. This was not the behaviour in earlier versions of this class and this counter-intuitive * behaviour led to problems. The problem was fixed using the {@link FieldTexts#MATCHNOTHING} specifier but introduced * another problem in that it was now much more difficult to build up a sequence of expressions separated by OR. Static * factory methods have been added to make this easier, see the examples below for current usage patterns. * *

Example 1 - Some of the fieldtext is fixed and can be added at construction time: * *

 *    FieldTextBuilder builder = new FieldTextBuilder(new MATCH("FIELD", "VALUE"));
*

*

or: *

*

 *    FieldTextBuilder builder = FieldTextBuilder.from(new MATCH("FIELD", "VALUE"));
*

*

Example 2 - Creating a sequence of ORs with multiple expressions (likewise for AND and XOR): *

*

 *    FieldTextBuilder builder = FieldTexts.OR(
 *        new MATCH("FIELD", "VALUE"),
 *        new EQUAL("NUMERIC", 7, 8),
 *        new EXISTS("UUID")
 *    );
*

*

Example 3 - Conditionally building ORs when none of the fieldtext is fixed (likewise for AND and XOR): *

*

 *    // Create a builder suitable for appending via OR
 *    FieldTextBuilder builder = FieldTexts.OR();
 *
 *    for (fieldText : fieldTextList) {
 * builder.OR(fieldText);
 *    }
 *
 *    if (builder.isMatchNothing()) {
 *        // Nothing was added as part of the loop so the fieldtext won't match any documents
 *    }
 * 
*/ public final class FieldTextBuilder extends AbstractFieldText { private final StringBuilder fieldTextString = new StringBuilder(); private int componentCount; private String lastOperator; // Values: null (no last operator), "" (unknown last operator), "AND", "OR", "XOR", "WHEN", "WHENn" private boolean not; // Creating an empty FieldTextBuilder leads to confusing behaviour under boolean operators. Given this class is // final there's no need to worry about subclasses, so the static factory methods can be used instead to create // 'empty' builders FieldTextBuilder() {} /** * Creates a {@code FieldTextBuilder} containing the specified fieldtext. The new object will not be backed by the * {@code FieldText} provided. * * @param fieldText The initial contents of the builder. */ public FieldTextBuilder(final FieldText fieldText) { Validate.notNull(fieldText, "FieldText should not be null"); setFieldText(fieldText); } /** * Converts a {@link FieldText} object into a {@code FieldTextBuilder}. This method can be more efficient than using * the equivalent constructor but the returned object might be backed by the {@link FieldText} object provided. * * @param fieldText The fieldtext expression to convert * @return An equivalent instance of {@code FieldTextBuilder} */ public static FieldTextBuilder from(final FieldText fieldText) { return (fieldText instanceof FieldTextBuilder) ? (FieldTextBuilder)fieldText : new FieldTextBuilder(fieldText); } /** * Appends the specified fieldtext onto the builder using the AND operator. Parentheses are omitted where possible * and logical simplifications are made in certain cases: *

*

(A AND A) {@literal =>} A
*
(* AND A) {@literal =>} A
*
(0 AND A) {@literal =>} 0
* * @param fieldText A fieldtext expression or specifier. * @return {@code this} */ @Override public FieldTextBuilder AND(final FieldText fieldText) { Validate.notNull(fieldText, "FieldText should not be null"); if(fieldText == this || toString().equals(fieldText.toString())) { return this; } if(isEmpty()) { return setFieldText(fieldText); } if(fieldText.isEmpty()) { return this; } if(MATCHNOTHING.equals(this) || MATCHNOTHING.equals(fieldText)) { return setFieldText(MATCHNOTHING); } return binaryOperation("AND", fieldText); } /** * Appends the specified fieldtext onto the builder using the OR operator. Parentheses are omitted where possible * and logical simplifications are made in certain cases: *

*

(A OR A) {@literal =>} A
*
(* OR A) {@literal =>} *
*
(0 OR A) {@literal =>} A
* * @param fieldText A fieldtext expression or specifier. * @return {@code this} */ @Override public FieldTextBuilder OR(final FieldText fieldText) { Validate.notNull(fieldText, "FieldText should not be null"); if(fieldText == this || toString().equals(fieldText.toString())) { return this; } if(isEmpty() || MATCHNOTHING.equals(fieldText)) { return this; } if(fieldText.isEmpty() || MATCHNOTHING.equals(this)) { return setFieldText(fieldText); } return binaryOperation("OR", fieldText); } /** * Appends the specified fieldtext onto the builder using the XOR operator. Parentheses are omitted where possible * and logical simplifications are made in certain cases: *

*

(A XOR A) {@literal =>} 0
*
(* XOR A) {@literal =>} NOT A
*
(0 XOR A) {@literal =>} A
* * @param fieldText A fieldtext expression or specifier. * @return {@code this}. */ @Override public FieldTextBuilder XOR(final FieldText fieldText) { Validate.notNull(fieldText, "FieldText should not be null"); if(fieldText == this || toString().equals(fieldText.toString())) { return setFieldText(MATCHNOTHING); } if(isEmpty()) { return setFieldText(fieldText).NOT(); } if(fieldText.isEmpty()) { return NOT(); } if(MATCHNOTHING.equals(this)) { return setFieldText(fieldText); } if(MATCHNOTHING.equals(fieldText)) { return this; } return binaryOperation("XOR", fieldText); } /** * Appends the specified fieldtext onto the builder using the WHEN operator. A simplification is made in the case * where the passed {@code fieldText} is equal to {@code this}: *

*

(A WHEN A) {@literal =>} A
* * @param fieldText A fieldtext expression or specifier. * @return {@code this} */ @Override public FieldTextBuilder WHEN(final FieldText fieldText) { Validate.notNull(fieldText, "FieldText should not be null"); Validate.isTrue(fieldText.size() >= 1, "FieldText must have a size greater or equal to 1"); if(size() < 1) { throw new IllegalStateException("Size must be greater than or equal to 1"); } // Here we assume that A WHEN A => A but is there an edge case where this doesn't work? if(fieldText == this || toString().equals(fieldText.toString())) { return this; } if(MATCHNOTHING.equals(this) || MATCHNOTHING.equals(fieldText)) { return setFieldText(MATCHNOTHING); } return binaryOperation("WHEN", fieldText); } /** * Appends the specified fieldtext onto the builder using the WHENn operator. * * @param depth The n in WHENn * @param fieldText A fieldtext expression or specifier. * @return {@code this}. */ @Override public FieldTextBuilder WHEN(final int depth, final FieldText fieldText) { Validate.isTrue(depth >= 1, "Depth must be at least 1"); Validate.notNull(fieldText, "FieldText should not be null"); Validate.isTrue(fieldText.size() >= 1, "FieldText must have a size greater or equal to 1"); if(size() < 1) { throw new IllegalStateException("Size must be greater than or equal to 1"); } // We omit this 'optimization' because for large n it doesn't work, X WHENn A => 0, even if X = A //if (fieldText == this || toString().equals(fieldText.toString())) { // return this; //} if(fieldText == this) { return binaryOperation("WHEN" + depth, new FieldTextWrapper(this)); } if(MATCHNOTHING.equals(this) || MATCHNOTHING.equals(fieldText)) { return setFieldText(MATCHNOTHING); } return binaryOperation("WHEN" + depth, fieldText); } /** * Clears any existing fieldtext and replaces it with the specified fieldtext. * * @param fieldText * @return */ private FieldTextBuilder setFieldText(final FieldText fieldText) { return clear().binaryOperation(null, fieldText); } /** * Does the work for the OR, AND, XOR and WHEN methods. * * @param operator * @param fieldText * @return */ private FieldTextBuilder binaryOperation(final String operator, final FieldText fieldText) { // This should never happen as the AND, OR, XOR and WHEN methods should already have checked this Validate.isTrue(fieldText != this); // Optimized case when fieldText is a FieldTextBuilder if(fieldText instanceof FieldTextBuilder) { return binaryOp(operator, (FieldTextBuilder)fieldText); } // Special case when we're empty if(componentCount == 0) { fieldTextString.append(fieldText.toString()); if(fieldText.size() > 1) { lastOperator = ""; } } else { // Add the NOTs, parentheses and operator addOperator(operator); // Size 1 means a single specifier, so no parentheses if(fieldText.size() == 1) { fieldTextString.append(fieldText.toString()); } else { fieldTextString.append('(').append(fieldText.toString()).append(')'); } } componentCount += fieldText.size(); not = false; return this; } /** * Does the work for the OR, AND and XOR methods in the optimized case where fieldText is a FieldTextBuilder. * * @param operator * @param fieldText * @return */ private FieldTextBuilder binaryOp(final String operator, final FieldTextBuilder fieldText) { // Special case when we're empty if(componentCount == 0) { // Just copy the argument fieldTextString.append(fieldText.fieldTextString); lastOperator = fieldText.lastOperator; not = fieldText.not; componentCount = fieldText.componentCount; } else { // Add the NOTs, parentheses and operator addOperator(operator); if(fieldText.lastOperator == null || fieldText.lastOperator.equals(operator) || fieldText.not) { fieldTextString.append(fieldText.toString()); } else { fieldTextString.append('(').append(fieldText.toString()).append(')'); } componentCount += fieldText.size(); not = false; } return this; } /** * The first section of the fieldtext manipulation process, where the NOTs are handled and the operator is added, * are common to both binaryOp methods so this method saves us repeating the code. * * @param operator */ private void addOperator(final String operator) { // This should never happen but sanity check... Validate.notNull(operator); if(lastOperator == null) { lastOperator = operator; } /* Logic to determine whether to put parentheses around the existing fieldtext. This is tied to the use of the * NOT operator as it also adds parentheses. */ if(not) { if(componentCount == 1) { fieldTextString.insert(0, "NOT+"); } else { fieldTextString.insert(0, "NOT(").append(')'); } } else if(!lastOperator.equals(operator)) { fieldTextString.insert(0, '(').append(')'); } lastOperator = operator; fieldTextString.append('+').append(operator).append('+'); } /** * Applies a NOT operator to the {@code FieldTextBuilder}. Note that two calls to {@code NOT()} will cancel out. * * @return {@code this} */ @Override public FieldTextBuilder NOT() { // NOT * => 0 // NOT 0 => * if(isEmpty()) { return setFieldText(MATCHNOTHING); } if(MATCHNOTHING.equals(this)) { return clear(); } not ^= true; return this; } /** * The total number of fieldtext specifiers in the current expression. * * @return The size of the expression. */ @Override public int size() { return componentCount; } /** * Whether or not the builder is empty. * * @return {@code true} if and only if size is 0. */ @Override public boolean isEmpty() { return componentCount == 0; } /** * The {@code String} representation of the fieldtext expression, exactly as it should be sent to IDOL. * * @return The {@code String} representation. */ @Override public String toString() { if(!not) { return fieldTextString.toString(); } if(componentCount == 1) { return "NOT+" + fieldTextString; } return "NOT(" + fieldTextString + ')'; } /** * Clears all fields back to an empty builder. * * @return {@code this} */ public FieldTextBuilder clear() { fieldTextString.setLength(0); componentCount = 0; not = false; lastOperator = null; return this; } /** * Whether or not the builder is equal to {@link FieldTexts#MATCHNOTHING}. * * @return {@code true} if and only if {@code this} is equal to {@link FieldTexts#MATCHNOTHING MATCHNOTHING} */ // TODO: Promote to the FieldText interface? public boolean isMatchNothing() { return equals(MATCHNOTHING); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy