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}