001/* 002 * Copyright 2017 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.search.query; 017 018import java.util.Arrays; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.List; 022import java.util.function.Predicate; 023import java.util.stream.Collector; 024import java.util.stream.Collectors; 025 026import org.apache.commons.lang3.StringUtils; 027 028import org.ametys.core.util.LambdaUtils; 029 030/** 031 * Represents a search {@link Query} corresponding to the logical "or" between several other queries. 032 */ 033public class OrQuery implements Query 034{ 035 036 /** The list of queries. The queries on this list are distinct. */ 037 protected List<Query> _queries; 038 039 /** 040 * Build an OrQuery object. 041 * @param queries the queries. 042 */ 043 public OrQuery(Query... queries) 044 { 045 this(Arrays.asList(queries)); 046 } 047 048 /** 049 * Build an OrQuery object. 050 * @param queries the queries as a Collection. 051 */ 052 public OrQuery(Collection<Query> queries) 053 { 054 _queries = queries.stream().distinct().collect(Collectors.toList()); 055 } 056 057 /** 058 * Returns a {@link Collector} wih collects {@link Query Queries} into an OR query 059 * @return a {@link Collector} wih collects {@link Query Queries} into an OR query 060 */ 061 public static Collector<Query, ?, OrQuery> collector() 062 { 063 return LambdaUtils.Collectors.withListAccumulation(OrQuery::new); 064 } 065 066 /** 067 * Get the list of queries in this "or". 068 * @return the list of queries. 069 */ 070 public List<Query> getQueries() 071 { 072 return Collections.unmodifiableList(_queries); 073 } 074 075 @Override 076 public String build() throws QuerySyntaxException 077 { 078 Predicate<Query> isMatchNone = MatchNoneQuery.class::isInstance; 079 List<Query> queriesWithoutMatchNone = _queries 080 .stream() 081 .filter(isMatchNone.negate()) 082 .collect(Collectors.toList()); 083 if (!_queries.isEmpty() && queriesWithoutMatchNone.isEmpty()) 084 { 085 // special case where all are MatchNone 086 return new MatchNoneQuery().build(); 087 } 088 089 boolean isFirst = true; 090 StringBuilder sb = new StringBuilder(); 091 092 for (Query subQuery : queriesWithoutMatchNone) 093 { 094 if (subQuery instanceof MatchAllQuery) 095 { 096 // short-circuit => all docs will match 097 return new MatchAllQuery().build(); 098 } 099 else if (subQuery != null) 100 { 101 String exprAsString = subQuery.build(); 102 if (StringUtils.isNotBlank(exprAsString)) 103 { 104 if (!isFirst) 105 { 106 sb.append(" OR "); 107 } 108 sb.append("(").append(exprAsString).append(")"); 109 isFirst = false; 110 } 111 } 112 } 113 114 if (isFirst) 115 { 116 return ""; 117 } 118 else 119 { 120 return sb.toString(); 121 } 122 } 123 124 @Override 125 public String toString(int indent) 126 { 127 String tagName = _tagNameForToString(); 128 final String orLineIndent = StringUtils.repeat(' ', indent); 129 final int subIndent = indent + 2; 130 final String subQueries = _queries 131 .stream() 132 .map(sq -> sq.toString(subIndent)) 133 .collect(Collectors.joining("\n")); 134 return orLineIndent + "[" + tagName + "]\n" + subQueries + "\n" + orLineIndent + "[/" + tagName + "]"; 135 } 136 137 /** 138 * The tag name for {@link #toString(int)} debug method. 139 * @return The tag name for {@link #toString(int)} debug method. 140 */ 141 protected String _tagNameForToString() 142 { 143 return "OR"; 144 } 145 146 @Override 147 public int hashCode() 148 { 149 final int prime = 31; 150 int result = 1; 151 result = prime * result + ((_queries == null) ? 0 : _queries.hashCode()); 152 return result; 153 } 154 155 @Override 156 public boolean equals(Object obj) 157 { 158 if (this == obj) 159 { 160 return true; 161 } 162 if (obj == null) 163 { 164 return false; 165 } 166 if (getClass() != obj.getClass()) 167 { 168 return false; 169 } 170 OrQuery other = (OrQuery) obj; 171 if (_queries == null) 172 { 173 if (other._queries != null) 174 { 175 return false; 176 } 177 } 178 else if (!_queries.equals(other._queries)) 179 { 180 return false; 181 } 182 return true; 183 } 184}