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.Objects; 023import java.util.function.Predicate; 024import java.util.stream.Collector; 025import java.util.stream.Collectors; 026 027import org.apache.commons.lang3.StringUtils; 028 029import org.ametys.core.util.LambdaUtils; 030 031/** 032 * Represents a search {@link Query} corresponding to the logical "or" between several other queries. 033 */ 034public class OrQuery implements Query 035{ 036 037 /** The list of queries. The queries on this list are distinct. */ 038 protected List<Query> _queries; 039 040 /** 041 * Build an OrQuery object. 042 * @param queries the queries. 043 */ 044 public OrQuery(Query... queries) 045 { 046 this(Arrays.asList(queries)); 047 } 048 049 /** 050 * Build an OrQuery object. 051 * @param queries the queries as a Collection. 052 */ 053 public OrQuery(Collection<Query> queries) 054 { 055 _queries = queries.stream().distinct().collect(Collectors.toList()); 056 } 057 058 /** 059 * Returns a {@link Collector} wih collects {@link Query Queries} into an OR query 060 * @return a {@link Collector} wih collects {@link Query Queries} into an OR query 061 */ 062 public static Collector<Query, ?, OrQuery> collector() 063 { 064 return LambdaUtils.Collectors.withListAccumulation(OrQuery::new); 065 } 066 067 /** 068 * Get the list of queries in this "or". 069 * @return the list of queries. 070 */ 071 public List<Query> getQueries() 072 { 073 return Collections.unmodifiableList(_queries); 074 } 075 076 @Override 077 public String build() throws QuerySyntaxException 078 { 079 Predicate<Query> isMatchNone = MatchNoneQuery.class::isInstance; 080 List<Query> queriesWithoutMatchNone = _queries 081 .stream() 082 .filter(isMatchNone.negate()) 083 .collect(Collectors.toList()); 084 if (!_queries.isEmpty() && queriesWithoutMatchNone.isEmpty()) 085 { 086 // special case where all are MatchNone 087 return new MatchNoneQuery().build(); 088 } 089 090 boolean isFirst = true; 091 StringBuilder sb = new StringBuilder(); 092 093 for (Query subQuery : queriesWithoutMatchNone) 094 { 095 if (subQuery instanceof MatchAllQuery) 096 { 097 // short-circuit => all docs will match 098 return new MatchAllQuery().build(); 099 } 100 else if (subQuery != null) 101 { 102 String exprAsString = subQuery.build(); 103 if (StringUtils.isNotBlank(exprAsString)) 104 { 105 if (!isFirst) 106 { 107 sb.append(" OR "); 108 } 109 sb.append("(").append(exprAsString).append(")"); 110 isFirst = false; 111 } 112 } 113 } 114 115 if (isFirst) 116 { 117 return ""; 118 } 119 else 120 { 121 return sb.toString(); 122 } 123 } 124 125 @Override 126 public String toString(int indent) 127 { 128 String tagName = _tagNameForToString(); 129 final String orLineIndent = StringUtils.repeat(' ', indent); 130 final int subIndent = indent + 2; 131 final String subQueries = _queries 132 .stream() 133 .filter(Objects::nonNull) 134 .map(sq -> sq.toString(subIndent)) 135 .collect(Collectors.joining("\n")); 136 return orLineIndent + "[" + tagName + "]\n" + subQueries + "\n" + orLineIndent + "[/" + tagName + "]"; 137 } 138 139 /** 140 * The tag name for {@link #toString(int)} debug method. 141 * @return The tag name for {@link #toString(int)} debug method. 142 */ 143 protected String _tagNameForToString() 144 { 145 return "OR"; 146 } 147 148 @Override 149 public int hashCode() 150 { 151 final int prime = 31; 152 int result = 1; 153 result = prime * result + ((_queries == null) ? 0 : _queries.hashCode()); 154 return result; 155 } 156 157 @Override 158 public boolean equals(Object obj) 159 { 160 if (this == obj) 161 { 162 return true; 163 } 164 if (obj == null) 165 { 166 return false; 167 } 168 if (getClass() != obj.getClass()) 169 { 170 return false; 171 } 172 OrQuery other = (OrQuery) obj; 173 if (_queries == null) 174 { 175 if (other._queries != null) 176 { 177 return false; 178 } 179 } 180 else if (!_queries.equals(other._queries)) 181 { 182 return false; 183 } 184 return true; 185 } 186}