/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/search/basic/NotNormalizer.java,v 1.8 2004/12/07 17:51:16 luetzkendorf Exp $
 * $Revision: 1.8 $
 * $Date: 2004/12/07 17:51:16 $
 *
 * ====================================================================
 *
 * Copyright 1999 The Apache Software Foundation 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.apache.slide.search.basic;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.slide.content.NodeProperty.NamespaceCache;
import org.apache.slide.search.BadQueryException;
import org.apache.slide.search.InvalidQueryException;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Namespace;

/**
 * This class recursivly removes all <code>&lt;not&gt;</code> expressions
 * and replaces their successors by appropriate negated expressions. Since
 * an example explains more than a thousand words:
 *
 * <pre>
 * &lt;D:not&gt;
 *    &lt;D:or&gt;
 *       &lt;D:lt&gt;
 *          &lt;D:prop&gt;
 *             &lt;D:getcontentlength&gt;
 *          &lt;/D:prop&gt;
 *          &lt;D:literal&gt;1000&lt;/D:literal&gt;
 *       &lt;/D:lt&gt;
 *       &lt;D:eq&gt;
 *          &lt;D:prop&gt;
 *             &lt;D:getcontenttype&gt;
 *          &lt;/D:prop&gt;
 *          &lt;D:literal&gt;text/xml&lt;/D:literal&gt;
 *       &lt;/D:eq&gt;
 *    &lt;/D:or&gt;
 * &lt;/D:not&gt;
 * </pre>
 *
 * will be transformed to
 *
 * <pre>
 * &lt;D:and&gt;
 *    &lt;D:not-lt&gt;
 *       &lt;D:prop&gt;
 *          &lt;D:getcontentlength&gt;
 *       &lt;/D:prop&gt;
 *       &lt;D:literal&gt;1000&lt;/D:literal&gt;
 *    &lt;/D:not-lt&gt;
 *    &lt;D:not-eq&gt;
 *       &lt;D:prop&gt;
 *          &lt;D:getcontenttype&gt;
 *       &lt;/D:prop&gt;
 *       &lt;D:literal&gt;text/xml&lt;/D:literal&gt;
 *    &lt;/D:not-eq&gt;
 * &lt;/D:and&gt;
 * </pre>
 *
 * @version $Revision: 1.8 $
 *
 **/
public class NotNormalizer {
    
    /**
     * The message of the {@link org.apache.slide.search.InvalidQueryException
     * InvalidQueryException} which is thrown by {@link #getQueryWithoutNotExpression
     * getQueryWithoutNotExpression} if a <code>&lt;not&gt;</code> expression has
     * NOT exactly one successor.
     */
    public static final String BAD_NOT_EXPRESSION_EXCEPTION_MESSAGE =
        "not expression must contain exactly one nested expression";
    
    /**
     * Returns the transformed query where all <code>&lt;not&gt;</code> elements
     * has been removed.
     *
     * @param      expressionElement  the (root) Element of the query to transform.
     *
     * @return     the transformed query where all <code>&lt;not&gt;</code> elements
     *             has been removed.
     *
     * @throws     BadQueryException  if transformin the query failed
     *                                (see {@link #BAD_NOT_EXPRESSION_EXCEPTION_MESSAGE
     *                                 BAD_NOT_EXPRESSION_EXCEPTION_MESSAGE}).
     */
    public Element getQueryWithoutNotExpression(Element expressionElement) throws BadQueryException {
        return getProcessedElement(expressionElement, false);
    }
    
    /**
     * Returns the appropriate replacement for the given <code>expressionElement</code>.
     * If <code>negate</code> is true, the Element has to be negated.
     *
     * @param      expressionElement  the Element for which to return the
     *                                appropriate replacement.
     * @param      negate             if <code>negate</code> is true, the given
     *                                Element has to be negated.
     *
     * @return     the appropriate replacement for the given <code>expressionElement</code>.
     *
     * @throws     BadQueryException  if the Element could not be processed.
     */
    protected Element getProcessedElement(Element expressionElement, boolean negate) throws BadQueryException {
        
        Element result = null;
        
        if ( Literals.NOT.equals(expressionElement.getName()) &&
            NamespaceCache.DEFAULT_URI.equals(expressionElement.getNamespaceURI()) ) {
            
            List children = expressionElement.getChildren();
            if (children.size() != 1) {
                throw new InvalidQueryException(BAD_NOT_EXPRESSION_EXCEPTION_MESSAGE);
            }
            return getProcessedElement((Element)children.get(0), ! negate);
        }
        
        if (negate) {
            result = getNegatedQueryElement(expressionElement);
        }
        else {
            result = getNamedClone(expressionElement, expressionElement.getName(), expressionElement.getNamespace());
        }
        Iterator childrenIterator = expressionElement.getChildren().iterator();
        while (childrenIterator.hasNext()) {
            result.addContent(getProcessedElement((Element)childrenIterator.next(), negate));
        }
        
        return result;
    }
    
    /**
     * Returns the negation of the given <code>expressionElement</code>.
     *
     * @param      expressionElement  the Element to return the appropriate
     *                                negation for.
     *
     * @return     the negation of the given <code>expressionElement</code>.
     *
     * @throws     BadQueryException  if the Element could not be processed.
     */
    protected Element getNegatedQueryElement(Element expressionElement) throws BadQueryException {
        
        if (NamespaceCache.DEFAULT_URI.equals(expressionElement.getNamespaceURI())) {
            return getNegatedDAVQueryElement(expressionElement);
        }
        if (NamespaceCache.SLIDE_URI.equals(expressionElement.getNamespaceURI())) {
            return getNegatedSlideQueryElement(expressionElement);
        }
        else {
            return getNegatedUnknownQueryElement(expressionElement);
        }
    }
    
    
    /**
     * Returns the negation of the given <code>expressionElement</code>,
     * which is neither in <code>DAV:</code> nor
     * <code>http://jakarta.apache.org/slide/</code> namespace.
     *
     * @param      expressionElement  the Element to return the appropriate
     *                                negation for.
     *
     * @return     the negation of the given <code>expressionElement</code>.
     *
     * @throws     BadQueryException  if the Element could not be processed.
     */
    protected Element getNegatedUnknownQueryElement(Element expressionElement) throws BadQueryException {
        return getNamedClone(expressionElement, expressionElement.getName(), expressionElement.getNamespace());
    }
    
    
    private static Map DAV_NEGATIONS = new HashMap(); 
    private static Map SLIDE_NEGATIONS = new HashMap();
    static {
        DAV_NEGATIONS.put(Literals.AND, Literals.OR);
        DAV_NEGATIONS.put(Literals.OR, Literals.AND);
        DAV_NEGATIONS.put(Literals.GT, Literals.NOT_GT);
        DAV_NEGATIONS.put(Literals.NOT_GT, Literals.GT);
        DAV_NEGATIONS.put(Literals.GTE, Literals.NOT_GTE);
        DAV_NEGATIONS.put(Literals.NOT_GTE, Literals.GTE);
        DAV_NEGATIONS.put(Literals.LT, Literals.NOT_LT);
        DAV_NEGATIONS.put(Literals.NOT_LT, Literals.LT);
        DAV_NEGATIONS.put(Literals.LTE, Literals.NOT_LTE);
        DAV_NEGATIONS.put(Literals.NOT_LTE, Literals.LTE);
        DAV_NEGATIONS.put(Literals.EQ, Literals.NOT_EQ);
        DAV_NEGATIONS.put(Literals.NOT_EQ, Literals.EQ);
        DAV_NEGATIONS.put(Literals.CONTAINS, Literals.NOT_CONTAINS);
        DAV_NEGATIONS.put(Literals.NOT_CONTAINS, Literals.CONTAINS);
        DAV_NEGATIONS.put(Literals.ISCOLLECTION, Literals.NOT_ISCOLLECTION);
        DAV_NEGATIONS.put(Literals.NOT_ISCOLLECTION, Literals.ISCOLLECTION);
        DAV_NEGATIONS.put(Literals.ISDEFINED, Literals.NOT_ISDEFINED);
        DAV_NEGATIONS.put(Literals.NOT_ISDEFINED, Literals.ISDEFINED);
        DAV_NEGATIONS.put(Literals.LIKE, Literals.NOT_LIKE);
        DAV_NEGATIONS.put(Literals.NOT_LIKE, Literals.LIKE);
        
        SLIDE_NEGATIONS.put(Literals.ISPRINCIPAL, Literals.NOT_ISPRINCIPAL);
        SLIDE_NEGATIONS.put(Literals.NOT_ISPRINCIPAL, Literals.ISPRINCIPAL);
        SLIDE_NEGATIONS.put(Literals.PROPCONTAINS, Literals.NOT_PROPCONTAINS);
        SLIDE_NEGATIONS.put(Literals.NOT_PROPCONTAINS, Literals.PROPCONTAINS);
        SLIDE_NEGATIONS.put("property-contains", "not-property-contains");
        SLIDE_NEGATIONS.put("not-property-contains", "property-contains");
        SLIDE_NEGATIONS.put("between", "not-between");
        SLIDE_NEGATIONS.put("not-between", "between");
    }
    
    /**
     * Returns the negation of the given <code>expressionElement</code>,
     * which is in the <code>DAV:</code> namespace.
     *
     * @param      expressionElement  the Element to return the appropriate
     *                                negation for.
     *
     * @return     the negation of the given <code>expressionElement</code>.
     *
     * @throws     BadQueryException  if the Element could not be processed.
     */
    protected Element getNegatedDAVQueryElement(Element expressionElement) 
        throws BadQueryException {
        
        String name = expressionElement.getName();
        
        String negated = (String)DAV_NEGATIONS.get(name);
        if (negated != null) {
            return getNamedClone(expressionElement, negated, 
                    NamespaceCache.DEFAULT_NAMESPACE);
        } else {
            return getNamedClone(expressionElement, name, 
                    expressionElement.getNamespace());
        }
    }
    
    /**
     * Returns the negation of the given <code>expressionElement</code>,
     * which is in the <code>http://jakarta.apache.org/slide/</code> namespace.
     *
     * @param      expressionElement  the Element to return the appropriate
     *                                negation for.
     *
     * @return     the negation of the given <code>expressionElement</code>.
     *
     * @throws     BadQueryException  if the Element could not be processed.
     */
    protected Element getNegatedSlideQueryElement(Element expressionElement) throws BadQueryException {
        
        String name = expressionElement.getName();
        String negated = (String)SLIDE_NEGATIONS.get(name);
        if (negated != null) {
            return getNamedClone(expressionElement, negated, 
                    NamespaceCache.SLIDE_NAMESPACE);
        } else {
            return getNamedClone(expressionElement, name, 
                    expressionElement.getNamespace());
        }
    }
    
    /**
     * Creates a new Element with the given <code>newName</code> in the
     * <code>newNamespace</code> and sets the value (text) and attributes
     * of the given <code>source</code> element.
     *
     * @param      source        the Element from which to copy the value and attributes.
     * @param      newName       the name to use for the Element to create.
     * @param      newNamespace  the Namespace to use for the Element to create.
     *
     * @return     the created Element.
     */
    protected Element getNamedClone(Element source, String newName, Namespace newNamespace) {
        
        Element copy = new Element(newName, newNamespace);
        copy.setText(source.getText());
        Iterator it = source.getAttributes().iterator();
    
        while (it.hasNext()) {
            Attribute attr = (Attribute)it.next();
            copy.setAttribute ((Attribute)attr.clone());
        }
        
        return copy;
    }
}

