/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/search/basic/BasicExpressionCompiler.java,v 1.12 2005/06/20 12:56:09 luetzkendorf Exp $
 * $Revision: 1.12 $
 * $Date: 2005/06/20 12:56:09 $
 *
 * ====================================================================
 *
 * 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.ArrayList;
import java.util.Collection;
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.PropertyProvider;
import org.jdom.Element;

/**
 * The implementation of the {@link org.apache.slide.search.basic.IBasicExpressionCompiler
 * IBasicExpressionCompiler} interface.
 *
 * @version $Revision: 1.12 $
 *
 **/
public class BasicExpressionCompiler implements IBasicExpressionCompiler{
    
    /**
     * The IBasicQuery that provides some general information about the query.
     */
    protected IBasicQuery query = null;
    
    /**
     * The PropertyProvider to use (if set).
     */
    protected PropertyProvider propertyProvider = null;
    
    /**
     * The default IBasicExpressionFactory to use.
     */
    protected IBasicExpressionFactory genericExpressionFactory = null;
    
    /**
     * The store specific IBasicExpressionFactory for property expressions
     * to use (if one exists).
     */
    protected IBasicExpressionFactory propertiesExpressionFactory = null;
    
    /**
     * The store specific IBasicExpressionFactory for content expressions
     * to use (if one exists).
     */
    protected IBasicExpressionFactory contentExpressionFactory = null;
    
    /**
     * Inidicates if it has already been tried to instantiate the
     * {@link #storeSpecificExpressionFactory storeSpecificExpressionFactory}.
     */
    protected boolean alreadyTriedToInstantiate = false;
    
    
    
    /**
     * Creates the BasicExpressionCompiler, loads and inits the expression
     * factories. If no store / indexer specific factory is defined, the
     * generic expression factory is used.
     *
     * @param    query               the  IBasicQuery.
     * @param    propertyProvider    the  PropertyProvider to use (may be
     *                               <code>null</code>).
     */
    public BasicExpressionCompiler (IBasicQuery query, PropertyProvider propertyProvider)  throws BadQueryException {
        this.query = query;
        this.propertyProvider = propertyProvider;
        
        genericExpressionFactory = new BasicExpressionFactory();
        
        propertiesExpressionFactory = query.getPropertiesExpressionFactory() == null ?
            genericExpressionFactory : query.getPropertiesExpressionFactory();
        
        contentExpressionFactory = query.getContentExpressionFactory() == null ?
            genericExpressionFactory : query.getContentExpressionFactory();

        propertiesExpressionFactory.init(query, propertyProvider);
        contentExpressionFactory.init(query, propertyProvider);
        genericExpressionFactory.init(query, propertyProvider);
    }
    

    
    /**
     * Compiles an IBasicExpression (-tree) from the given <code>expressionElement</code>.
     *
     * @param      expressionElement  the (root) expression Element to compile
     *                                into an IBasicExpression.
     *
     * @return     the compiled IBasicExpression.
     *
     * @throws     BadQueryException  if compiling the expression failed.
     */
    public IBasicExpression compile(Element expressionElement) throws BadQueryException {
        
        if (isMergeExpression(expressionElement)) {
            
            List expressionsToMerge = new ArrayList();
            Iterator iterator = expressionElement.getChildren().iterator();
            while (iterator.hasNext()) {
                expressionsToMerge.add(compile((Element)iterator.next()));
            }
            return createMergeExpression(expressionElement.getName(),
                                         expressionElement.getNamespaceURI(),
                                         expressionsToMerge);
        }
        else {
            return createExpression(expressionElement);
        }
    }
    
    /**
     * Creates a MergeExpression for the given element (AND, OR). The given children
     * are the expressions to merge.
     * If there are expressions of different factories (content, properties or
     * generic), all expressions of one factory are merged using its factory,
     * then the remaining nodes are merged using generic factory.
     *
     * @param    name                the name of the Element describing the merge expression.
     * @param    namespace           the namespace of the Element describing the merge expression.
     * @param    expressionsToMerge  the expressions to merge.
     *
     * @return   an IBasicExpression
     *
     * @throws   BadQueryException
     */
    private IBasicExpression createMergeExpression (String name, String namespace,
                                                    List expressionsToMerge)
        throws BadQueryException
    {
        if (expressionsToMerge.size() == 0) {
            throw new BadQueryException("DAV:" + name + " must not be empty!");
        }
        
        Map expressionsByFactory = new HashMap ();
        IBasicExpression mergeExpression = null;
        Iterator it = expressionsToMerge.iterator();
        
        // collect all expressions by factory (should be max 2 different factories)
        while (it.hasNext()) {
            IBasicExpression exp = (IBasicExpression)it.next();
            IBasicExpressionFactory fac = exp.getFactory();
            List expList = (List)expressionsByFactory.get(fac);
            if (expList == null) {
                expList = new ArrayList();
                expressionsByFactory.put (fac, expList);
            }
            
            expList.add (exp);
        }
        
        // merge all expressions of each factory
        it = expressionsByFactory.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            IBasicExpressionFactory fac = (IBasicExpressionFactory)entry.getKey();
            List expList = (List)entry.getValue();
            
            if (expList.size() > 1) {
                // merge all expressions
                expressionsByFactory.put
                    (fac, fac.createMergeExpression (name, namespace, expList));
            } else {
                // we're interested in the one and only expression
                expressionsByFactory.put (fac, expList.get(0));
            }
        }
        
        // if there is more than one entry in the map (expressions of different
        // factories), merge them with GenericFactory
        Collection exprToMerge = expressionsByFactory.values();
        
        if (expressionsByFactory.size() > 1) {
            
            mergeExpression =
                genericExpressionFactory.createMergeExpression (name, namespace,
                                                                exprToMerge);
        }
        else {
            it = expressionsByFactory.values().iterator();
            if (it.hasNext())
                mergeExpression = (IBasicExpression)it.next();
        }
        
        return mergeExpression;
    }
    
    /**
     * Creates a (non-merge) expression (compare...) for the given Element.
     * Falls back to generic factory if no store / indexer specific factory
     * defined. Uses generic factory if specific factory does not implement
     * the expression.
     *
     * @param    expressionElement  an Element describing the expression.
     *
     * @return   an IBasicExpression
     *
     * @throws   BadQueryException
     */
    private IBasicExpression createExpression (Element expressionElement)
        throws BadQueryException
    {
        IBasicExpression expression = null;
        
        if (isContentExpression (expressionElement)) {
            expression = contentExpressionFactory.createExpression(expressionElement);
        }
        else {
            expression = propertiesExpressionFactory.createExpression(expressionElement);
        }

        if (expression == null) {
            expression = genericExpressionFactory.createExpression (expressionElement);
        }
        
        return expression;
    }
    
    
    /**
     * Returns <code>true</code> if the given <code>expressionElement</code>
     * describes a merge expression.
     *
     * @param      expressionElement  the Element that describes the expression.
     *
     * @return     <code>true</code> if the given <code>expressionElement</code>
     *             describes a merge expression.
     */
    public static boolean isMergeExpression(Element expressionElement) {
        
        boolean isMerge = false;
        if ( (expressionElement != null) &&
            NamespaceCache.DEFAULT_URI.equals(expressionElement.getNamespaceURI()) ) {
            
            isMerge =  Literals.AND.equals(expressionElement.getName()) ||
                Literals.OR.equals(expressionElement.getName());
        }
        return isMerge;
    }
    
    
    /**
     * Checks, if an expression is a contains expression.
     *
     * TODO: ask the contentIndexer, if it knows this expression (to allow
     *       proprietary extensions to DASL, for example <RDFContains>, <xpath>)
     *
     * @param    expressionElement   an Element
     *
     * @return   a boolean
     *
     */
    public static boolean isContentExpression (Element expressionElement) {
        boolean isContent = false;
        if ( (expressionElement != null) &&
            NamespaceCache.DEFAULT_URI.equals(expressionElement.getNamespaceURI()) ) {
            
            isContent =  Literals.CONTAINS.equals(expressionElement.getName()) ||
                         Literals.NOT_CONTAINS.equals(expressionElement.getName());
        }
        return isContent;
    }
}

