/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/resourcekind/AbstractResourceKind.java,v 1.38 2005/02/02 11:34:18 luetzkendorf Exp $
 * $Revision: 1.38 $
 * $Date: 2005/02/02 11:34:18 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 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.webdav.util.resourcekind;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Transformer;

import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.PropertyName;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.webdav.util.AclConstants;
import org.apache.slide.webdav.util.BindConstants;
import org.apache.slide.webdav.util.DaslConstants;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.WebdavConstants;
import org.apache.slide.webdav.util.properties.InvalidLivePropertyValueException;
import org.apache.slide.webdav.util.properties.PropertyComputer;
import org.apache.slide.webdav.util.properties.PropertyDefaultProvider;
import org.apache.slide.webdav.util.properties.PropertyValueValidator;

/**
 * Abstraction of a WebDAV-compliant resource kind.
 */
abstract public class AbstractResourceKind implements ResourceKind, WebdavConstants, DeltavConstants, AclConstants, DaslConstants, BindConstants {
    
    /**
     ** String constant for an empty (<code>""</code>) string.
     **/
    public static final String EMPTY_STRING = "";
    
    // protected properties
    protected static Set liveProperties = new HashSet();
    
    // protected properties
    protected static Set protectedProperties = new HashSet();
    
    // computed properties
    protected static Set computedProperties = new HashSet();
    
    /**
     * Factory method.
     */
    static public ResourceKind getInstance() {
        return null;
    }
    
    /**
     * Factory method.
     */
    static public ResourceKind determineResourceKind( NamespaceAccessToken nsaToken, NodeRevisionDescriptors nrds, NodeRevisionDescriptor nrd ) {
        return ResourceKindManager.determineResourceKind(nsaToken, nrds, nrd);
    }
    
    /**
     * Factory method.
     */
    static public ResourceKind determineResourceKind( NamespaceAccessToken nsaToken, String resourcePath, NodeRevisionDescriptor nrd ) {
        return ResourceKindManager.determineResourceKind(nsaToken, resourcePath, nrd);
    }
      
    /**
     * Return true if the specified property is a DAV: live property.
     */
    public static boolean isLiveProperty(String propName) {
        return( liveProperties.contains(PropertyName.getPropertyName(propName)));
    }
    public static boolean isLiveProperty(PropertyName propName) {
        return( liveProperties.contains(propName));
    }
    
    /**
     * Return true if the specified property is a protected DAV: live property.
     */
    public static boolean isProtectedProperty(String propName) {
        return(protectedProperties.contains(PropertyName.getPropertyName(propName)));
    }
    /**
     * Returns <code>true</code> if the given property is protected at any resource kind.
     * @param propName property name to be tested.
     */
    static boolean isGlobalProtectedProperty(PropertyName propName) {
        return(protectedProperties.contains(propName));
    }

    /**
     * Return true if the specified property is a computed DAV: live property.
     */
    public static boolean isComputedProperty(String propName) {
        return(computedProperties.contains(PropertyName.getPropertyName(propName)));
    }
    /**
     * Return the set of all DAV: live properties.
     */
    public static Set getAllLiveProperties() {
        Set result = new HashSet();
        
        CollectionUtils.addAll(result, IteratorUtils.transformedIterator(
                liveProperties.iterator(),
                PROPERTYNAME_TO_LOCALNAME));
        
        return result;
    }
    
    /**
     * Return the set of all DAV: protected live properties.
     */
    public static Set getAllProtectedProperties() {
        Set result = new HashSet();
        
        CollectionUtils.addAll(result, IteratorUtils.transformedIterator(
                protectedProperties.iterator(),
                PROPERTYNAME_TO_LOCALNAME));
        
        return result;
    }
    
    /**
     * Return the set of all DAV: computed live properties.
     */
    public static Set getAllComputedProperties() {
        Set result = new HashSet();
        
        CollectionUtils.addAll(result, IteratorUtils.transformedIterator(
                computedProperties.iterator(),
                PROPERTYNAME_TO_LOCALNAME));
        
        return result;
    }
    
    
    
    // -------------------------------------------------------------------------
    private Set myLiveProperties = new HashSet();
    private Set myProtectedProperties = new HashSet();
    private Set myComputedProperties = new HashSet();
    private Set mySupportedReports = new HashSet();
    private Set mySupportedMethods = new HashSet();
    private Map myPropertyComputers = new HashMap();
    private Map myPropertyValidators = new HashMap(); 
    private Map myDefaultProviders = new HashMap();
    
    private Set supportedLiveProperties = null;
    private Set supportedProtectedProperties = null;
    private Set supportedComputedProperties = null;
    private Set supportedReports = null;
    private Set supportedMethods = null;
    private Map propertyComputers = null;
    private Map propertyValidators = null;
    private Map defaultProviders = null;
    
    void addComputedProperty(PropertyName name, PropertyComputer computer) {
        if (computer == null) throw new NullPointerException();
        this.myComputedProperties.add(name);
        this.myPropertyComputers.put(name, computer);
        computedProperties.add(name);
    }
    void addProtectedProperty(PropertyName name) {
        this.myProtectedProperties.add(name);
        protectedProperties.add(name);
    }
    void addLiveProperty(PropertyName name) {
        this.myLiveProperties.add(name);
        liveProperties.add(name);
    }
    void addSupportedReport(String name) {
        this.mySupportedReports.add(name);
    }
    void addSupportedMethod(String name) {
        this.mySupportedMethods.add(name.toUpperCase());
    }
    void addPropertyValidator(PropertyName name, PropertyValueValidator validator) {
        if (validator == null) throw new NullPointerException();
        this.myPropertyValidators.put(name, validator);
    }
    void addDefaultProvider(PropertyName name, PropertyDefaultProvider defaultProvider) {
        if (defaultProvider == null) throw new NullPointerException();
        this.myDefaultProviders.put(name, defaultProvider);
    }
    
    // -------------------------------------------------------------------------
    
    
    /**
     * Get the set properties supported by this resource kind.
     */
    public Set getSupportedLiveProperties() {
        Set result = new HashSet();
        
        CollectionUtils.addAll(result, IteratorUtils.transformedIterator(
                getSupportedLivePropertyNames().iterator(),
                PROPERTYNAME_TO_LOCALNAME));
        
        return result;
    }
    
    /**
     * Return true, if the specified property is supported by this resource kind.
     */
    public boolean isSupportedLiveProperty(String prop) {
        return getSupportedLivePropertyNames().contains(
                PropertyName.getPropertyName(prop));
    }
    public boolean isSupportedLiveProperty(PropertyName name) {
        return getSupportedLivePropertyNames().contains(name);
    }
    
    public boolean isProtectedProperty(PropertyName name) {
        return getSupportedProtectedPropertyNames().contains(name);
    }
    
    public boolean isComputedProperty(PropertyName propName) {
        return(getSupportedComputedPropertyNames().contains(propName));
    }
    
    public Set getSupportedLivePropertyNames() {
        if (this.supportedLiveProperties == null) {
            this.supportedLiveProperties = new HashSet();
            for(Iterator i = getSuperKinds().iterator(); i.hasNext();) {
                AbstractResourceKind kind =(AbstractResourceKind)i.next();
                this.supportedLiveProperties.addAll(kind.getSupportedLivePropertyNames());
            }
            this.supportedLiveProperties.addAll(this.myLiveProperties);
        } 
        return this.supportedLiveProperties;
    }
    
    public Set getSupportedProtectedPropertyNames() {
        if (this.supportedProtectedProperties == null) {
            this.supportedProtectedProperties = new HashSet();
            for(Iterator i = getSuperKinds().iterator(); i.hasNext();) {
                AbstractResourceKind kind =(AbstractResourceKind)i.next();
                this.supportedProtectedProperties.addAll(kind.getSupportedProtectedPropertyNames());
            }
            this.supportedProtectedProperties.addAll(this.myProtectedProperties);
        } 
        return this.supportedProtectedProperties;
    }
    public Set getSupportedComputedPropertyNames() {
        if (this.supportedComputedProperties == null) {
            this.supportedComputedProperties = new HashSet();
            for(Iterator i = getSuperKinds().iterator(); i.hasNext();) {
                AbstractResourceKind kind =(AbstractResourceKind)i.next();
                this.supportedComputedProperties.addAll(kind.getSupportedComputedPropertyNames());
            }
            this.supportedComputedProperties.addAll(this.myComputedProperties);
        } 
        return this.supportedComputedProperties;
    }
    
    /**
     * Get the set properties supported by this resource kind.
     * @param filter Q_PROTECTED_ONLY or Q_COMPUTED_ONLY (no filtering if null)
     * @see org.apache.slide.webdav.util.WebdavConstants
     * @see org.apache.slide.webdav.util.DeltavConstants
     * @see org.apache.slide.webdav.util.AclConstants
     * @see org.apache.slide.webdav.util.DaslConstants
     */
    public Set getSupportedLiveProperties( String filter ) {
        if( Q_COMPUTED_ONLY.equals(filter) ) {
            Set result = new HashSet();
            CollectionUtils.addAll(result, IteratorUtils.transformedIterator(
                    getSupportedComputedPropertyNames().iterator(),
                    PROPERTYNAME_TO_LOCALNAME));
            return result;
        } else if( Q_PROTECTED_ONLY.equals(filter) ) {
            Set result = new HashSet();
            CollectionUtils.addAll(result, IteratorUtils.transformedIterator(
                    getSupportedProtectedPropertyNames().iterator(),
                    PROPERTYNAME_TO_LOCALNAME));
            return result;
        }
        throw new IllegalArgumentException();
    }
    
    public Set getSupportedLivePropertyNames( String filter ) {
        if( Q_COMPUTED_ONLY.equals(filter) ) {
            return getSupportedComputedPropertyNames();
        } else if( Q_PROTECTED_ONLY.equals(filter) ) {
            return getSupportedProtectedPropertyNames();
        }
        throw new IllegalArgumentException();
    }
    
    
    /**
     * Get the set properties supported by this resource kind.
     * @param excludedFeatures array of F_* constants (no filtering if null or empty)
     * @see org.apache.slide.webdav.util.WebdavConstants
     * @see org.apache.slide.webdav.util.DeltavConstants
     * @see org.apache.slide.webdav.util.AclConstants
     * @see org.apache.slide.webdav.util.DaslConstants
     */
    public Set getSupportedLiveProperties( String[] excludedFeatures ) {
        throw new UnsupportedOperationException();
//        Set s = new HashSet();
//        Iterator it = getSuperKinds().iterator();
//        while( it.hasNext() ) {
//            ResourceKind superkind = (ResourceKind)it.next();
//            s.addAll( superkind.getSupportedLiveProperties(excludedFeatures) );
//        }
//        return s;
    }
    
    /**
     * Get the set properties supported by this resource kind.
     * @param filter Q_PROTECTED_ONLY or Q_COMPUTED_ONLY (no filtering if null)
     * @param excludedFeatures array of F_* constants (no filtering if null or empty)
     * @see org.apache.slide.webdav.util.WebdavConstants
     * @see org.apache.slide.webdav.util.DeltavConstants
     * @see org.apache.slide.webdav.util.AclConstants
     * @see org.apache.slide.webdav.util.DaslConstants
     */
    public Set getSupportedLiveProperties( String filter, String[] excludedFeatures ) {
        throw new UnsupportedOperationException();
//        Set s = getSupportedLiveProperties( excludedFeatures );
//        if( Q_COMPUTED_ONLY.equals(filter) ) {
//            s.retainAll( computedProperties );
//        }
//        if( Q_PROTECTED_ONLY.equals(filter) ) {
//            s.retainAll( protectedProperties );
//        }
//        return s;
    }
    
    public PropertyComputer getPropertyComputer(String name) {
        return getPropertyComputer(PropertyName.getPropertyName(name));
    }
    
    public PropertyComputer getPropertyComputer(PropertyName name) {
        return (PropertyComputer)getPropertyComputers().get(name);
    }
    
    Map getPropertyComputers() {
        if (this.propertyComputers == null) {
            this.propertyComputers = new HashMap();
            
            for(Iterator i = getSuperKinds().iterator(); i.hasNext();) {
                AbstractResourceKind kind =(AbstractResourceKind)i.next();
                this.propertyComputers.putAll(kind.getPropertyComputers());
            }
            this.propertyComputers.putAll(this.myPropertyComputers);
        }
        return this.propertyComputers;
    }
    
    Map getPropertyValidators() {
        if (this.propertyValidators == null) {
            this.propertyValidators = new HashMap();
            
            for(Iterator i = getSuperKinds().iterator(); i.hasNext(); ) {
                AbstractResourceKind kind =(AbstractResourceKind)i.next();
                this.propertyValidators.putAll(kind.getPropertyValidators());
            }
            this.propertyValidators.putAll(this.myPropertyValidators);
        }
        return this.propertyValidators;
    }
    
    public Map getDefaultProviders() {
        if (this.defaultProviders == null) {
            this.defaultProviders = new HashMap();
            
            for(Iterator i = getSuperKinds().iterator(); i.hasNext(); ) {
                AbstractResourceKind kind =(AbstractResourceKind)i.next();
                this.defaultProviders.putAll(kind.getDefaultProviders());
            }
            this.defaultProviders.putAll(this.myDefaultProviders);
            
            this.defaultProviders = Collections.unmodifiableMap(this.defaultProviders);
        }
        return this.defaultProviders;
    }
    
    /**
     * Get the set methods supported by this resource kind.
     */
    public Set getSupportedMethods() {
        if (this.supportedMethods == null) {
            this.supportedMethods = new HashSet();
        
            for(Iterator i = getSuperKinds().iterator(); i.hasNext(); ) {
                AbstractResourceKind kind = (AbstractResourceKind)i.next();
                this.supportedMethods.addAll(kind.getSupportedMethods());
            }
            
            this.supportedMethods.addAll(this.mySupportedMethods);
        }
        return this.supportedMethods;
    }
    /**
     * Return true, if the specified method is supported by this resource kind.
     */
    public boolean isSupportedMethod(String method) {
        return getSupportedMethods().contains(method);
    }
    


    
    /**
     * Get the set reports supported by this resource kind.
     */
    public Set getSupportedReports() {
        if (this.supportedReports == null) {
            this.supportedReports = new HashSet();
        
            for(Iterator i = getSuperKinds().iterator(); i.hasNext(); ) {
                AbstractResourceKind kind = (AbstractResourceKind)i.next();
                this.supportedReports.addAll(kind.getSupportedReports());
            }
            
            this.supportedReports.addAll(this.mySupportedReports);
        }
        return this.supportedReports;
    }
    
    /**
     * Some properties (e.g. <code>&lt;auto-version&gt;</code>) have a
     * restricted set of supported values.
     * If the value set of the given <code>property</code> is restricted and
     * the given <code>value</code> is not contained in that set, this method
     * returns <code>false</code>, otherwise <code>true</code>.
     *
     * @param      propertyName  the name of the property.
     * @param      value         the value to check.
     *
     * @return     <code>false</code> if the value is not allowed, otherwise
     *             <code>true</code>.
     */
    public boolean isSupportedPropertyValue(String propertyName, Object value) {
        return isSupportedPropertyValue(
                PropertyName.getPropertyName(propertyName), value);
//        boolean isSupported = true;
//        List listOfRestrictedValues = (List)RESTRICTED_PROPERTY_VALUE_MAP.get(propertyName);
//        if (listOfRestrictedValues != null) {
//            
//            if (value == null) {
//                return false;
//            }
//            
//            // handle "" value
//            if (EMPTY_STRING.equals(value.toString())) {
//                return listOfRestrictedValues.contains(EMPTY_STRING);
//            }
//            
//            XMLValue xmlValue = null;
//            if (value instanceof XMLValue) {
//                xmlValue = (XMLValue)value;
//            }
//            else {
//                try {
//                    xmlValue = new XMLValue(value.toString());
//                }
//                catch (JDOMException e) {
//                    return false;
//                }
//            }
//            isSupported =
//                (xmlValue.size() > 0) &&
//                listOfRestrictedValues.contains(((Element)xmlValue.iterator().next()).getName());
//        }
//        
//        return isSupported;
    }
    
    public boolean isSupportedPropertyValue(PropertyName propertyName, Object value) {
        PropertyValueValidator validator = 
            (PropertyValueValidator)getPropertyValidators().get(propertyName);
        
        if (validator != null) {
            try {
                validator.checkPropertyValue(value);
            } catch (InvalidLivePropertyValueException e) {
                return false;
            }
        }
        
        return true;
    }
    
    public PropertyDefaultProvider getDefaultProvider(PropertyName property) {
        return (PropertyDefaultProvider)getDefaultProviders().get(property);
    }
    
    /**
     *
     */
    public String toString() {
        return plainClassName( getClass() );
    }
    
    private List superKinds = null;
    /**
     * Returns a list of {@link ResourceKind} objects that represent all 
     * <em>super kinds</em>. 
     */
    protected List getSuperKinds() {
        if (superKinds == null) {
            List result = new ArrayList();
            Class myclass = getClass();
            String myclassName = plainClassName( myclass );
            Class[] ifs = myclass.getInterfaces();
            for( int i = 0; i < ifs.length; i++ ) {
                Class myif = ifs[i];
                String myifName = plainClassName( myif );
                if( !myclassName.startsWith(myifName) )
                    continue;
                Class[] superifs = myif.getInterfaces();
                for( int j = 0; j < superifs.length; j++ ) {
                    Class superif = superifs[j];
                    String superifName = plainClassName( superif );
                    if( "ResourceKind".equals(superifName) )
                        continue;
                    Class superclass = null;
                    try {
                        superclass = Class.forName( superif.getName()+"Impl" );
                        Class[] ptypes = new Class[0];
                        Method facmeth = superclass.getMethod( "getInstance", ptypes );
                        Object[] parms = new Object[0];
                        result.add( facmeth.invoke(null, parms) );
                    }
                    catch( Exception x ) {
                        x.printStackTrace();
                        throw new IllegalStateException( x.getMessage() );
                    }
                }
            }
            this.superKinds = result;
        }
        
        return this.superKinds;
    }
    
    private String plainClassName( Class c ) {
        String n = c.getName();
        int i = n.lastIndexOf( '.' );
        return n.substring( i + 1 );
    }
    
    private static Transformer PROPERTYNAME_TO_LOCALNAME = new Transformer() {
        public Object transform(Object propertyName) {
            return ((PropertyName)propertyName).getName();
        }
    };
    
    /**
     *
     */
    public static void main(String[] args) {
        String rkn = args[0];
        Class[] pt = new Class[0];
        Object[] p = new Object[0];
        Iterator i;
        
        if( rkn == null || rkn.length() == 0 )
            return;
        try {
            Class rkc = Class.forName(
                "org.apache.slide.webdav.util.resourcekind."+rkn+"Impl");
            ResourceKind rk =
                (ResourceKind)rkc.getMethod("getInstance", pt).invoke(null, p);
            System.out.println("\nResource kind: "+rk);
            System.out.println("\nSupported live properties:");
            i = rk.getSupportedLiveProperties().iterator();
            while( i.hasNext() )
                System.out.println("- "+i.next());
            System.out.println("\nSupported methods:");
            i = rk.getSupportedMethods().iterator();
            while( i.hasNext() )
                System.out.println("- "+i.next());
            System.out.println("\nSupported reports:");
            i = rk.getSupportedReports().iterator();
            while( i.hasNext() )
                System.out.println("- "+i.next());
        }
        catch( Exception x ) { x.printStackTrace(); }
    }
}

