001/*
002 *  Copyright 2014 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.Map;
019import java.util.Optional;
020
021import org.apache.commons.lang3.StringUtils;
022
023/**
024 * Represents a {@link Query} corresponding to the logical negation of another {@link Query}.
025 */
026public class NotQuery extends AbstractWrapperQuery
027{
028    /** The prefix for making a query negative. */
029    public static final String NEGATION_QUERY_PREFIX = "*:* -";
030    
031    /**
032     * Build a NotQuery object.
033     * @param query the negated query.
034     */
035    public NotQuery(Query query)
036    {
037        super(query);
038    }
039    
040    /**
041     * Appends a negation to the query being built.
042     * <br>This method just does <code>sb.append({@value #NEGATION_QUERY_PREFIX});</code>
043     * @param query The query builder
044     * @return The given query builder
045     */
046    public static StringBuilder appendNegation(StringBuilder query)
047    {
048        query.append(NEGATION_QUERY_PREFIX);
049        return query;
050    }
051    
052    @Override
053    public String build() throws QuerySyntaxException
054    {
055        String queryString = getSubQuery().build();
056        if (queryString.isEmpty())
057        {
058            return "";
059        }
060        else
061        {
062            return NEGATION_QUERY_PREFIX + "(" + queryString + ")";
063        }
064    }
065    
066    public Optional<Object> buildAsJson() throws QuerySyntaxException
067    {
068        Optional<Query> rewrittenQueryOptional = getSubQuery().rewrite();
069        if (rewrittenQueryOptional.isEmpty())
070        {
071            return Optional.empty();
072        }
073        
074        Query rewrittenQuery = rewrittenQueryOptional.get();
075        if (rewrittenQuery instanceof NotQuery nq)
076        {
077            return nq.getSubQuery().buildAsJson();
078        }
079        else
080        {
081            return rewrittenQuery.buildAsJson()
082                    .map(q -> Map.of("bool", Map.of("must", "*:*", "must_not", q)));
083        }
084    }
085    
086    public Optional<Query> rewrite()
087    {
088        Query subQuery = getSubQuery();
089        Optional<Query> rewrittenQueryOptional = subQuery.rewrite();
090        if (rewrittenQueryOptional.isEmpty())
091        {
092            return Optional.empty();
093        }
094        
095        Query rewrittenQuery = rewrittenQueryOptional.get();
096        if (rewrittenQuery == subQuery)
097        {
098            return Optional.of(this);
099        }
100        else if (rewrittenQuery instanceof NotQuery nq)
101        {
102            return Optional.of(nq.getSubQuery());
103        }
104        else
105        {
106            return Optional.of(new NotQuery(rewrittenQuery));
107        }
108    }
109    
110    @Override
111    public String toString(int indent)
112    {
113        final String notLineIndent = StringUtils.repeat(' ', indent);
114        final String subq = getSubQuery().toString(indent + 2);
115        return notLineIndent + "[NOT]\n" +  subq + "\n" + notLineIndent + "[/NOT]";
116    }
117}