/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/search/XBasicExpression.java,v 1.4 2004/09/15 14:58:25 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2004/09/15 14:58:25 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 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.store.tamino.datastore.search;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.Uri;
import org.apache.slide.search.BadQueryException;
import org.apache.slide.search.InvalidQueryException;
import org.apache.slide.search.QueryScope;
import org.apache.slide.search.RequestedResource;
import org.apache.slide.search.SearchException;
import org.apache.slide.search.basic.IBasicExpression;
import org.apache.slide.search.basic.IBasicExpressionFactory;
import org.apache.slide.search.basic.IBasicResultSet;
import org.apache.slide.store.ResourceId;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.XConnection;
import org.apache.slide.store.tamino.datastore.XConnectionKey;
import org.apache.slide.store.tamino.datastore.XConnectionPool;
import org.apache.slide.store.tamino.datastore.XUtilDBAccessor;
import org.apache.slide.store.tamino.datastore.schema.KnownPropertyList;
import org.apache.slide.store.tamino.datastore.schema.XKnownPropertyHelper;
import org.apache.slide.store.tamino.store.ISlideAccessor;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.XException;
import org.jdom.Attribute;
import org.jdom.Element;

/**
 * An XBasicExpression represents exactly one executable Tamino query
 * It may have the state resolved or unresolved. If it is resolved, it
 * contains a Set of RequestedResource, that represents the result set of
 * this XBasicExpression.
 *
 * @author <a href="mailto:martin.wallmer@softwareag.com">Martin Wallmer</a>
 * @version $Revision: 1.4 $
 */
public abstract class XBasicExpression implements IBasicExpression {
    
    /** for accessing the store specific params */
    protected ISlideAccessor slideAccessor;
    
    /** the query to which this query belongs */
    protected XBasicQuery    query;
    
    /** helps to get knownPorperties */
    protected KnownPropertyList knownProperties;
    
    /** the expression element */
    protected Element expressionElement;
    
    private String uncutSlidePath;
    private boolean needPostProcessing;
    
    /** backptr to the factory */
    IBasicExpressionFactory factory;
    
    /**
     * prevent from being instantiated from outside, use factory method
     */
    protected XBasicExpression () {}
    
    /**
     * constructor. Only called by the conrecte expressions
     *
     * @param e the jdom element representing this expression.
     */
    protected XBasicExpression (Element e) throws InvalidQueryException {
        this.expressionElement = e;
        List attrList = e.getAttributes();
        if (attrList.size() != 0) {
            Attribute attr = (Attribute) attrList.get(0);
            throw new InvalidQueryException (attr.getQualifiedName() +
                                                 " is an unprocessable entity");
        }
    }
    
    /**
     * Initializes a query expression
     *
     * @param    slideAccessor       an ISlideAccessor
     * @param    query               a  XBasicQuery
     *
     * @throws   BadQueryException
     *
     */
    public void init(ISlideAccessor slideAccessor, XBasicQuery query) throws BadQueryException {
        this.slideAccessor = slideAccessor;
        knownProperties = XKnownPropertyHelper.getKnownPropertyList (getTsdLanguage (slideAccessor));
        this.query = query;
    }
    
    /**
     * Method getTsdLanguage
     *
     * @param    s                   an ISlideAccessor
     *
     * @return   a String
     *
     * @throws   BadQueryException
     *
     */
    private String getTsdLanguage (ISlideAccessor s) throws BadQueryException {
        XConnectionKey key;
        
        try {
            key = new XConnectionKey (s, "getTsdLanguage");
        } catch (XException e) {
            throw new BadQueryException (e);
        }
        String result;
        try {
            XConnection xcon = XConnectionPool.getInstance().getXConnection (key);
            
            XUtilDBAccessor acc = new XUtilDBAccessor (xcon.getTConnection());
            result = acc.getSchemaLanguage(XGlobals.META_COLLECTION);
            xcon.close();
            
        } catch (XDatastoreException e) {
            throw new BadQueryException (e);
        }
        
        return result;
    }
    
    /**
     * executes this basic expression. It must be guaranteed, that all
     * subsequent expressions can be expressed in one Tamino Query.
     *
     * @return   an IBasicResultSet
     *
     * @throws   SearchException
     *
     */
    public abstract IBasicResultSet execute () throws SearchException;
    
    /**
     * Creates the query string.
     *
     * @param    slidePath           a  String
     *
     * @return   a String
     *
     * @throws   InvalidScopeException
     *
     */
    protected abstract String createQueryString (String slidePath)
        throws SearchException;
    
    /**
     * Checks, if another expression may be merged (and, or) with this
     * expression into one Tamino query. Two IBasicExpressions may be mergbed if
     * they are both instanceof XBasicExpression and if they are both either
     * "Property Expression" or both "Content Expression".
     *
     * @param    otherExpression     an IBasicExpression
     *
     * @return   true if mergeable
     *
     */
    abstract boolean isMergeable (IBasicExpression otherExpression);
    
    /**
     * Checks, if all members of collection can be merged by an XMergeExpression
     * like OR, AND. Returns true, if the first child is an XBasicExpression
     * and all other children are mergeable with that.
     *
     * @param    expressions         a  Collection of IBasicExpressions
     *
     * @return   true if mergeable
     *
     */
    static boolean isMergeable (Collection expressions) {
        boolean result = true;
        
        Iterator it = expressions.iterator();
        IBasicExpression firstChild = (IBasicExpression)it.next();
        if (firstChild instanceof XBasicExpression) {
            while (it.hasNext()) {
                IBasicExpression nextChild = (IBasicExpression)it.next();
                if (((XBasicExpression)firstChild).isMergeable(nextChild) == false) {
                    result = false;
                    break;
                }
            }
        }
        else
            result = false;
        
        return result;
    }
    
    protected String getCollectionPrefix() {
        return slideAccessor.getParameter (XGlobals.TAMINO_COLLECTION);
    }
    
    /**
     * Creates the filter part of the query that reflects the scope. The calling method
     * must is responsible to add the collectionPrefix and the enclosing [] brackets.
     * The depth is taken into account here.
     *
     * @param    slidePath           a  String
     *
     * @return   a String
     *
     */
    protected String getScopeFilter (String slidePath) {
        if (Configuration.useBinding(query.getStore())) {
            return getBindingScopeFilter(slidePath);
        } else {
            return getNoneBindingScopeFilter(slidePath, "");
        }
    }
    
    private String getBindingScopeFilter (String slidePath) {
        Uri uri = slideAccessor.getNamespace().getUri(slidePath);
        if (uri != null && uri.isStoreRoot()) {
            String result = getNoneBindingScopeFilter(slidePath, ResourceId.RESOURCE_ID_SCHEMA);
//            System.out.println(result);
            return result;
        }
        else {
            return query.getDescriptorsMappingList().getScopeQuery();
        }
    }
    
    private String getNoneBindingScopeFilter (String slidePath, String uriScheme) {
        StringBuffer sb = new StringBuffer ();
        
        QueryScope scope = query.getScope();
        
        if (scope.isCollection()) {
            // cut off ending slash
            String withoutTrailingSlash =
                slidePath.endsWith("/") ?
                slidePath.substring (0, slidePath.length()-1) :
                slidePath;
            
            sb.append ("(@uri=\"").append(uriScheme);
            sb.append (withoutTrailingSlash).append ("\"");
            int depth = query.getProtectedDepth();
            if (depth > 0) {
                
                uncutSlidePath = slidePath;
                String cutOffSlidePath = slidePath;
                
                if (slidePath.length () > XGlobals.MAX_TEXT_SEARCH_SIZE) {
                    needPostProcessing = true;
                    cutOffSlidePath = slidePath.substring (0, XGlobals.MAX_TEXT_SEARCH_SIZE);
                }
                
                sb.append (" or @uri~=\"").append(uriScheme);
                sb.append (cutOffSlidePath).append ("*\")");
                
                if (depth != QueryScope.DEPTH_INFINITY) {
                    int absDepth = countSlashes (withoutTrailingSlash);
                    sb.append (" and @depth <= ");
                    sb.append (absDepth + depth);
                }
            }
            else {
                sb.append (")");
            }
        }
            
        else {
            sb.append ("@uri=\"").append(uriScheme);
            sb.append (slidePath).append ("\"");
        }
        
        if (scope.getIncludeSet() != null)
            setInclExclFilter (scope.getIncludeSet().iterator(), sb, false);
        
        if (scope.getExcludeSet() != null)
            setInclExclFilter (scope.getExcludeSet().iterator(), sb, true);
        
        
        String scopeFilter = sb.toString();
//        System.out.println (scopeFilter);
        
        return scopeFilter;
    }
    
    /**
     * Method getInclExclFilter
     *
     * @param    scope               a  QueryScope
     * @param    sb                  a  StringBuffer
     *
     * @return   a StringBuffer
     *
     */
    private void setInclExclFilter (Iterator it, StringBuffer sb, boolean isEx) {
        if (it.hasNext()) {
            if (isEx) {
                sb.append (" and not (");
            }
            else {
                sb.append (" and (");
            }
            while (it.hasNext()) {
                String pattern = ((String)it.next()).replace ('%', '*');
                sb.append ("@lastPathSegment~=\"" + pattern + "\"");
                if (it.hasNext())
                    sb.append (" or ");
            }
            sb.append (")");
        }
    }
    
    /**
     * Workaround for Tamino restriction: only 64 byte for text searches. If the
     * length of the slidePath is greater 62 byte, it is cut off. The query might
     * return more elements as specified by the scope. If necessary, they are
     * filtered out here.
     *
     * @param    resultSet           the unfiltered IBasicResultSet
     *
     * @return   the filtered IBasicResultSet
     *
     * @throws   SearchException
     *
     */
    protected IBasicResultSet postProcessor (IBasicResultSet resultSet) throws SearchException {
        IBasicResultSet result = resultSet;
        if (needPostProcessing) {
            Iterator it = resultSet.iterator();
            
            try {
                while (it.hasNext()) {
                    RequestedResource resource = (RequestedResource)it.next();
                    if (!resource.getUri().startsWith (uncutSlidePath)) {
                        it.remove();
                    }
                }
            }
            catch (SlideException e) {
                throw new SearchException (e);
            }
        }
        return resultSet;
    }
    
    private int countSlashes (String uri) {
        int noOfSlashes = 0;
        
        for (int i = 0; i < uri.length(); i++) {
            if (uri.charAt(i) == '/') {
                noOfSlashes ++;
            }
        }
        return noOfSlashes;
    }
    
    public void setFactory (IBasicExpressionFactory factory) {
        this.factory = factory;
    }
    
    public IBasicExpressionFactory getFactory() {
        return this.factory;
    }
}

