// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2006 by R. Pito Salas // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: BasicQuery.java,v 1.11 2008/02/28 09:58:32 spyromus Exp $ // package com.salas.bb.domain.query; import com.salas.bb.domain.query.articles.ArticleSentimentsProperty; import com.salas.bb.utils.i18n.Strings; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; /** * Base implementation of a query. It takes care of storing the criteria and type of * join. It knows nothing about the actual object types we will be matching. */ public abstract class BasicQuery implements IQuery { private ICriteria[] criteriaList; private boolean andQuery; private final List<IProperty> availableProperties; /** * Creates basic query. * * @param availableProperties properties, which can be used in this type of query. */ public BasicQuery(IProperty[] availableProperties) { this.availableProperties = Arrays.asList(availableProperties); andQuery = false; criteriaList = new ICriteria[0]; } // --------------------------------------------------------------------------------------------- // Working with criteria // --------------------------------------------------------------------------------------------- /** * Creates new criteria record and adds it to the tail of the criteria list. * * @return criteria record. */ public synchronized ICriteria addCriteria() { Criteria criteria = new Criteria(); ICriteria[] newCriteriaList = copyWithEmptySlot(1); int lastIndex = newCriteriaList.length - 1; newCriteriaList[lastIndex] = criteria; criteriaList = newCriteriaList; return criteria; } /** * Copies current criteria list and adds the slot in the tail for new criteria. * * @param slots number of slots to add to the tail. * * @return copy of list with a slot. */ private synchronized ICriteria[] copyWithEmptySlot(int slots) { int currentSize = criteriaList.length; ICriteria[] newCriteriaList = new ICriteria[currentSize + slots]; System.arraycopy(criteriaList, 0, newCriteriaList, 0, currentSize); return newCriteriaList; } /** * Removes criteria from the list. * * @param index index of criteria. * * @throws IndexOutOfBoundsException if index < 0 or index >= getCriteriaCount(). */ public synchronized void removeCriteria(int index) { if (index < 0 || index >= getCriteriaCount()) { throw new IndexOutOfBoundsException(MessageFormat.format( Strings.error("no.criteria.at.0"), index, getCriteriaCount())); } int currentSize = criteriaList.length; ICriteria[] newCriteriaList = new ICriteria[currentSize - 1]; System.arraycopy(criteriaList, 0, newCriteriaList, 0, index); System.arraycopy(criteriaList, index + 1, newCriteriaList, index, currentSize - index - 1); criteriaList = newCriteriaList; } /** * Returns the number of criteria records in this query. * * @return number of criteria records. */ public synchronized int getCriteriaCount() { return criteriaList.length; } /** * Returns criteria at the given index. * * @param index index. * * @return criteria. * * @throws IndexOutOfBoundsException if index < 0 or index >= getCriteriaCount(). */ public synchronized ICriteria getCriteriaAt(int index) { return criteriaList[index]; } /** * Returns TRUE if the query is in AND-mode. * * @return TRUE for AND-mode. */ public boolean isAndQuery() { return andQuery; } /** * Sets/resets the And/Or query flag. When set the query is following AND-logic when joining the * results. * * @param and TRUE to turn AND-mode on. */ public void setAndQuery(boolean and) { andQuery = and; } /** * Returns the list of all properties this query can operate. * * @return available properties. */ public Collection getAvailableProperties() { return Collections.unmodifiableList(availableProperties); } /** * Returns the property object by its descriptor. When you recreate query from some saved state * you need to use this method to get properties for stored criteria objects. * * @param descriptor descriptor which is previously returned by IProperty objects. * * @return property object or NULL if unknown. * * @throws NullPointerException if the object is not specified. */ public IProperty getPropertyByDescriptor(String descriptor) { if (descriptor == null) return null; for (IProperty property : availableProperties) { if (descriptor.equals(property.getDescriptor()) || descriptor.equals(property.getName())) return property; } return null; } // --------------------------------------------------------------------------------------------- // Basic query operations // --------------------------------------------------------------------------------------------- /** * Validates the query. The query is valid when all criteria records are valid. * * @param removeDuplicates <code>TRUE</code> to remove duplicate articles. * @param maxDupWords maximum first duplicate words to treat as a filter. * * @return error message or NULL if valid. @param removeDuplicates */ public synchronized String validate(boolean removeDuplicates, int maxDupWords) { String errorMessage = null; if (removeDuplicates && maxDupWords < 1) { errorMessage = Strings.message("smartfeed.type.validation.low.maxdupwords"); } else if (criteriaList.length > 0) { for (int i = 0; errorMessage == null && i < criteriaList.length; i++) { ICriteria criteria = criteriaList[i]; errorMessage = criteria.validate(); } } else errorMessage = Strings.message("query.is.empty"); return errorMessage; } /** * Returns TRUE if some object matches all/any query criteria, depending on the * <code>andQuery</code> switch. * * @param target target object. * * @return TRUE if matches. * * @throws ClassCastException if the object is off unacceptable type. * @throws NullPointerException if the object isn't sepcified. */ public boolean match(Object target) { boolean matching = false; boolean continuing = true; ICriteria[] criteriaCopy = copyWithEmptySlot(0); for (int i = 0; continuing && i < criteriaCopy.length; i++) { ICriteria criteria = criteriaCopy[i]; matching = isCriteriaMatching(criteria, target); continuing = isAndQuery() ? matching : !matching; } return matching; } /** * Checks if the target is matching given criteria. * * @param criteria criteria to check. * @param target target to match against. * * @return target. */ protected boolean isCriteriaMatching(ICriteria criteria, Object target) { String value = criteria.getValue(); if (value != null) value = value.trim().toLowerCase(); return criteria.getProperty().match(target, criteria.getComparisonOperation(), value); } /** * Returns TRUE if a criteria in this query refers to the sentiments property. * * @return TRUE if a criteria in this query refers to the sentiments property. */ public boolean hasSentimentsClause() { for (ICriteria criteria : criteriaList) { if (criteria.getProperty().getClass() == ArticleSentimentsProperty.class) return true; } return false; } /** * Compares this object to another one. * * @param o another object. * * @return <code>TRUE</code> if two objects are the same. */ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final BasicQuery basicQuery = (BasicQuery)o; if (andQuery != basicQuery.andQuery) return false; if (!Arrays.equals(criteriaList, basicQuery.criteriaList)) return false; return true; } /** * Returns the hash code. * * @return hash code. */ public int hashCode() { return getClass().getName().hashCode(); } }