/* * Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved. * * This file is part of the Jspresso framework. * * Jspresso is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Jspresso 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Jspresso. If not, see <http://www.gnu.org/licenses/>. */ package org.jspresso.framework.model.component.query; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.jspresso.framework.model.component.IQueryComponent; import org.jspresso.framework.model.descriptor.IPropertyDescriptor; import org.jspresso.framework.util.accessor.IAccessorFactory; import org.jspresso.framework.util.exception.NestedRuntimeException; /** * This is a utility service that knows how to match an arbitrary bean or map against a query component. * * @author Vincent Vandenschrick */ public class QueryComponentMatcher { private Map<String, Object> filteringConditions; private Map<String, QueryComponentMatcher> subMatchers; private IAccessorFactory accessorFactory; private boolean caseSensitive; private boolean matchesMiddle; /** * Instantiates a new Query component matcher. * * @param queryComponent * the query component * @param accessorFactory * the accessor factory * @param caseSensitive * the case sensitive * @param matchesMiddle * the matches middle */ public QueryComponentMatcher(IQueryComponent queryComponent, IAccessorFactory accessorFactory, boolean caseSensitive, boolean matchesMiddle) { this.subMatchers = new HashMap<>(); this.accessorFactory = accessorFactory; this.caseSensitive = caseSensitive; this.matchesMiddle = matchesMiddle; this.filteringConditions = computeFilteringConditions(queryComponent); } /** * Filter collection. * * @param collection * the collection * @return the list */ public List<?> filterCollection(List<?> collection) { List<Object> filtered = new ArrayList<>(); if (filteringConditions != null && !filteringConditions.isEmpty()) { for (Object element : collection) { if (componentMatches(element)) { filtered.add(element); } } } else { filtered.addAll(collection); } return filtered; } /** * Component matches. * * @param component * the component * @return the boolean */ public boolean componentMatches(Object component) { for (Map.Entry<String, Object> condition : filteringConditions.entrySet()) { if (!conditionMatches(component, condition.getKey(), condition.getValue())) { return false; } } return true; } /** * Condition matches. * * @param component * the component * @param propertyName * the property name * @param predicate * the predicate * @return the boolean */ protected boolean conditionMatches(Object component, String propertyName, Object predicate) { if (predicate instanceof IQueryComponent && ((IQueryComponent) predicate).isEmpty()) { return true; } String[] nestedProps = propertyName.split("\\."); Object property = component; for (String nestedProperty : nestedProps) { if (property != null) { try { property = getAccessorFactory().createPropertyAccessor(nestedProperty, property.getClass()).getValue( property); } catch (IllegalAccessException | NoSuchMethodException e) { throw new NestedRuntimeException(e); } catch (InvocationTargetException e) { throw new NestedRuntimeException(e.getTargetException()); } } } if (property instanceof Collection<?>) { for (Object propertyElement : (Collection<?>) property) { if (propertyMatches(component, propertyName, propertyElement, predicate)) { return true; } } } else { return propertyMatches(component, propertyName, property, predicate); } return false; } /** * Property matches. * * @param component * the component * @param propertyName * the property name * @param property * the property * @param predicate * the predicate * @return the boolean */ @SuppressWarnings({"unused", "unchecked"}) protected boolean propertyMatches(Object component, String propertyName, Object property, Object predicate) { if (predicate instanceof ComparableQueryStructure) { return ((ComparableQueryStructure) predicate).matches((Comparable<Object>) property); } else if (predicate instanceof IQueryComponent) { return getSubMatcher(propertyName, (IQueryComponent) predicate).componentMatches(property); } else if (predicate instanceof String) { String regex = (String) predicate; String[] disjunctions = regex.split(QueryComponent.DISJUNCT); if (disjunctions.length > 1) { for (String disjunctionRegex : disjunctions) { if (propertyMatches((String) property, disjunctionRegex)) { return true; } } return false; } else { String[] conjunctions = regex.split(QueryComponent.CONJUNCT); if (conjunctions.length > 1) { for (String conjunctionRegex : conjunctions) { if (!propertyMatches((String) property, conjunctionRegex)) { return false; } } return true; } else { return propertyMatches((String) property, regex); } } } return true; } /** * Property matches. * * @param property * the property * @param regex * the regex * @return the boolean */ protected boolean propertyMatches(String property, String regex) { boolean negate = false; if (regex.startsWith(QueryComponent.NOT_VAL)) { negate = true; regex = regex.substring(1); } boolean matches; if (regex.startsWith(QueryComponent.NULL_VAL)) { matches = (property == null || property.length() == 0); } else { if (property == null) { matches = false; } else { if (caseSensitive) { matches = Pattern.compile(regex).matcher(property).matches(); } else { matches = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(property).matches(); } } } if (negate) { matches = !matches; } return matches; } private Map<String, Object> computeFilteringConditions(IQueryComponent queryComponent) { Map<String, Object> conditions = new HashMap<>(); for (Map.Entry<String, Object> qcProp : queryComponent.entrySet()) { String k = qcProp.getKey(); Object v = qcProp.getValue(); IPropertyDescriptor pd = queryComponent.getQueryDescriptor().getPropertyDescriptor(k); if (pd != null && v != null && !(v instanceof ComparableQueryStructure && !((ComparableQueryStructure) v) .isRestricting())) { if (v instanceof String) { String regex = (String) v; regex = regex.replaceAll("%", ".*"); regex = regex.replaceAll("_", "."); if (!regex.contains(IQueryComponent.CONJUNCT) && !regex.contains(IQueryComponent.DISJUNCT) && !regex.contains( IQueryComponent.NOT_VAL) && !regex.contains(IQueryComponent.NULL_VAL)) { regex += ".*"; } if (matchesMiddle) { regex = ".*" + regex; } conditions.put(k, regex); } else { conditions.put(k, v); } } } return conditions; } private QueryComponentMatcher getSubMatcher(String propertyName, IQueryComponent subQueryComponent) { QueryComponentMatcher subMatcher = subMatchers.get(propertyName); if (subMatcher == null) { subMatcher = new QueryComponentMatcher(subQueryComponent, accessorFactory, caseSensitive, matchesMiddle); subMatchers.put(propertyName, subMatcher); } return subMatcher; } /** * Gets accessor factory. * * @return the accessor factory */ protected IAccessorFactory getAccessorFactory() { return accessorFactory; } }