001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.pojotools;
014
015import static org.apache.juneau.internal.StringUtils.*;
016
017import java.lang.reflect.*;
018import java.util.*;
019
020import org.apache.juneau.*;
021
022/**
023 * Designed to provide paging on POJOs consisting of arrays and collections.
024 *
025 * <p>
026 * Allows you to quickly return subsets of arrays and collections based on position/limit arguments.
027 */
028@SuppressWarnings({"rawtypes"})
029public final class PojoSearcher implements PojoTool<SearchArgs> {
030
031   /**
032    * Default reusable searcher.
033    */
034   public static final PojoSearcher DEFAULT = new PojoSearcher();
035
036   final MatcherFactory[] factories;
037
038   /**
039    * Constructor.
040    *
041    * @param factories The matcher factories to use.
042    */
043   public PojoSearcher(MatcherFactory...factories) {
044      this.factories = factories;
045   }
046
047   /**
048    * Constructor.
049    */
050   public PojoSearcher() {
051      this(NumberMatcherFactory.DEFAULT, TimeMatcherFactory.DEFAULT, StringMatcherFactory.DEFAULT);
052   }
053
054   @Override /* PojoTool */
055   public Object run(BeanSession session, Object input, SearchArgs args) {
056
057      ClassMeta<?> type = session.getClassMetaForObject(input);
058      Map<String,String> search = args.getSearch();
059
060      if (search.isEmpty() || type == null || ! type.isCollectionOrArray())
061         return input;
062
063      List<Object> l = null;
064      RowMatcher rowMatcher = new RowMatcher(session, search);
065
066      if (type.isCollection()) {
067         Collection c = (Collection)input;
068         l = new ArrayList<>(c.size());
069         for (Object o : c) {
070            if (rowMatcher.matches(o))
071               l.add(o);
072         }
073
074      } else /* isArray */ {
075         int size = Array.getLength(input);
076         l = new ArrayList<>(size);
077         for (int i = 0; i < size; i++) {
078            Object o = Array.get(input, i);
079            if (rowMatcher.matches(o))
080               l.add(o);
081         }
082      }
083
084      return l;
085   }
086
087   //====================================================================================================
088   // MapMatcher
089   //====================================================================================================
090   /*
091    * Matches on a Map only if all specified entry matchers match.
092    */
093   private class RowMatcher {
094
095      Map<String,ColumnMatcher> entryMatchers = new HashMap<>();
096      BeanSession bs;
097
098      RowMatcher(BeanSession bs, Map query) {
099         this.bs = bs;
100         for (Map.Entry e : (Set<Map.Entry>)query.entrySet())
101            entryMatchers.put(stringify(e.getKey()), new ColumnMatcher(bs, stringify(e.getValue())));
102      }
103
104      boolean matches(Object o) {
105         if (o == null)
106            return false;
107         ClassMeta<?> cm = bs.getClassMetaForObject(o);
108         if (cm.isMapOrBean()) {
109            Map m = cm.isMap() ? (Map)o : bs.toBeanMap(o);
110            for (Map.Entry<String,ColumnMatcher> e : entryMatchers.entrySet()) {
111               String key = e.getKey();
112               Object val = null;
113               if (m instanceof BeanMap) {
114                  val = ((BeanMap)m).getRaw(key);
115               } else {
116                  val = m.get(key);
117               }
118               if (! e.getValue().matches(val))
119                  return false;
120            }
121            return true;
122         }
123         if (cm.isCollection()) {
124            for (Object o2 : (Collection)o)
125               if (! matches(o2))
126                  return false;
127            return true;
128         }
129         if (cm.isArray()) {
130            for (int i = 0; i < Array.getLength(o); i++)
131               if (! matches(Array.get(o, i)))
132                  return false;
133            return true;
134         }
135         return false;
136      }
137   }
138
139   //====================================================================================================
140   // ObjectMatcher
141   //====================================================================================================
142   /*
143    * Matcher that uses the correct matcher based on object type.
144    * Used for objects when we can't determine the object type beforehand.
145    */
146   private class ColumnMatcher {
147
148      String searchPattern;
149      Matcher[] matchers;
150      BeanSession bs;
151
152      ColumnMatcher(BeanSession bs, String searchPattern) {
153         this.bs = bs;
154         this.searchPattern = searchPattern;
155         this.matchers = new Matcher[factories.length];
156      }
157
158      boolean matches(Object o) {
159         ClassMeta<?> cm = bs.getClassMetaForObject(o);
160         if (cm == null)
161            return false;
162         if (cm.isCollection()) {
163            for (Object o2 : (Collection)o)
164               if (matches(o2))
165                  return true;
166            return false;
167         }
168         if (cm.isArray()) {
169            for (int i = 0; i < Array.getLength(o); i++)
170               if (matches(Array.get(o, i)))
171                  return true;
172            return false;
173         }
174         for (int i = 0; i < factories.length; i++) {
175            if (factories[i].canMatch(cm)) {
176               if (matchers[i] == null)
177                  matchers[i] = factories[i].create(searchPattern);
178               return matchers[i].matches(cm, o);
179            }
180         }
181         return false;
182      }
183   }
184}