/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/content/NodeProperty.java,v 1.31 2005/01/21 16:16:32 luetzkendorf Exp $
 * $Revision: 1.31 $
 * $Date: 2005/01/21 16:16:32 $
 *
 * ====================================================================
 *
 * 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.content;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.apache.slide.common.Domain;
import org.apache.slide.common.PropertyName;
import org.apache.slide.security.NodePermission;
import org.apache.slide.util.EmptyEnumeration;
import org.jdom.Namespace;

/**
 * Node property class
 *
 * @version $Revision: 1.31 $
 */
public final class NodeProperty implements Serializable, Cloneable {
    
    
    // -------------------------------------------------------------- Constants
    
    
    public static final String DEFAULT_NAMESPACE = "DAV:";
    public static final String SLIDE_NAMESPACE =
        "http://jakarta.apache.org/slide/";
    
    protected static final String I_STANDARDLIVEPROPERTIESCLASS         = "standardLivePropertiesClass";
    protected static final String I_STANDARDLIVEPROPERTIESCLASS_DEFAULT = "org.apache.slide.webdav.util.resourcekind.AbstractResourceKind";
    
    /**
     * The standard live properties.
     */
    public static Set allLiveProperties;

    /**
     * The standard protected properties.
     */
    public static Set allProtectedProperties;

    /**
     * The standard computed properties.
     */
    public static Set allComputedProperties;
    
    static {
        try {
            if (Domain.isInitialized()) {
                Class slpc = Class.forName( Domain.getParameter(I_STANDARDLIVEPROPERTIESCLASS, I_STANDARDLIVEPROPERTIESCLASS_DEFAULT) );
                Method lp = slpc.getMethod( "getAllLiveProperties", new Class[]{} );
                allLiveProperties = (Set)lp.invoke( null, new Object[]{} ); // obj=null since method is static
                Method pp = slpc.getMethod( "getAllProtectedProperties", new Class[]{} );
                allProtectedProperties = (Set)pp.invoke( null, new Object[]{} ); // obj=null since method is static
                Method cp = slpc.getMethod( "getAllComputedProperties", new Class[]{} );
                allComputedProperties = (Set)cp.invoke( null, new Object[]{} ); // obj=null since method is static
            }
        }
        catch( Exception x ) {
            Domain.warn( "Loading of standard live properties class failed: "+x.getMessage() );
        }
        
        if( allLiveProperties == null )      allLiveProperties = Collections.EMPTY_SET;
        if( allProtectedProperties == null ) allProtectedProperties = Collections.EMPTY_SET;
        if( allComputedProperties == null )  allComputedProperties = Collections.EMPTY_SET;
    }
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Constructor.
     *
     * @param name Name
     * @param value Value
     */
    public NodeProperty(String name, Object value) {
        this(name, value, DEFAULT_NAMESPACE);
    }
    
    
    /**
     * Constructor.
     *
     * @param name Name
     * @param value Value
     * @param namespace Namespace
     */
    public NodeProperty(String name, Object value, String namespace) {
        //if (value == null) throw new NullPointerException();
        this.propertyName = PropertyName.getPropertyName(name, namespace);
        this.value = value == null ? "" : value;
        this.type = "";
        this.kind = determineKind(this.propertyName.getNamespace(), 
                this.propertyName.getName());
    }
    
    public NodeProperty(PropertyName name, Object value) {
        //if (value == null) throw new NullPointerException();
        this.propertyName = name;
        this.value = value == null ? "" : value;
        this.type = "";
        this.kind = determineKind(this.propertyName.getNamespace(), 
                this.propertyName.getName());
    }
    
    /**
     * Constructor.
     *
     * @param name Name
     * @param value Value
     * @param namespace Namespace
     * @param type the type
     */
    public NodeProperty(String name, Object value, String namespace, String type) {
        this(name, value, namespace);
        this.type = type;
    }
    
    
    /**
     * Constructor.
     *
     * @param name Name
     * @param value Value
     * @param protectedProperty Is the property protected?
     */
    public NodeProperty(String name, Object value, boolean protectedProperty) {
        this(name, value, DEFAULT_NAMESPACE);
        if( protectedProperty )
            this.kind = Kind.PROTECTED;
        else
            this.kind = determineKind( DEFAULT_NAMESPACE, name );
    }
    
    public NodeProperty(String name, Object value, String namespace, boolean protectedProperty) {
        this(name, value, namespace);
        if( protectedProperty )
            this.kind = Kind.PROTECTED;
        else
            this.kind = determineKind( namespace, name );
    }
    
    public NodeProperty(PropertyName name, Object value,  boolean protectedProperty) {
        this(name, value);
        if( protectedProperty )
            this.kind = Kind.PROTECTED;
        else
            this.kind = determineKind( name.getNamespace(), name.getName() );
    }
    
    /**
     * Constructor.
     *
     * @param name Name
     * @param value Value
     * @param namespace Namespace
     * @param type Type info
     * @param protectedProperty Protected property
     */
    public NodeProperty(String name, Object value, String namespace,
                        String type, boolean protectedProperty) {
        this(name, value, namespace);
        this.type = type;
        if( protectedProperty )
            this.kind = Kind.PROTECTED;
        else
            this.kind = determineKind( namespace, name );
    }
    
    
    
    // ----------------------------------------------------- Instance Variables
    
    /**
     * The properties name.
     */
    private PropertyName propertyName;
    
    /**
     * Property value.
     */
    private Object value;
    
    
    /**
     * Value Type. If the value is stored as a String representation of the
     * value, the type field is used.
     * FIXME : Remove that
     */
    private String type;
    
    
    /** The kind of property: dead, live, protected, computed */
    private Kind kind = Kind.DEAD;
    
    
    /**
     * Permission list.
     */
    private Vector permissions = null;
    
    
    // ------------------------------------------------------------- Properties
    
    /**
     * Determine the kind of the property given by the specified namespace and name.
     */
    private static Kind determineKind( String namespace, String name ) {
        Kind result = Kind.DEAD;
        
        if( DEFAULT_NAMESPACE.equals(namespace) ) {
            if( allComputedProperties.contains(name) )
                result = Kind.COMPUTED;
            else if( allProtectedProperties.contains(name) )
                result = Kind.PROTECTED;
            else if( allLiveProperties.contains(name) )
                result = Kind.LIVE;
        }
        return result;
    }
    
    /**
     * Kind accessor.
     *
     * @return true, if this is a dead property
     */
    public boolean isDeadProperty() {
        return( (this.kind == Kind.DEAD) );
    }
    
    
    /**
     * Kind accessor.
     *
     * @return true, if this is a computed (live) property
     */
    public boolean isComputed() {
        return( (this.kind == Kind.COMPUTED) );
    }
    
    
    /**
     * Kind accessor.
     *
     * @return true, if this is a protected (live) property
     */
    public boolean isProtected() {
        return((this.kind == Kind.PROTECTED) || (this.kind == Kind.COMPUTED));
    }
    
    
    /**
     * Kind accessor.
     *
     * @return true, if this is a live property
     */
    public boolean isLiveProperty() {
        return((this.kind == Kind.LIVE) 
                || (this.kind == Kind.PROTECTED) 
                || (this.kind == Kind.COMPUTED) );
    }
    
    
    /**
     * Property name accessor.
     *
     * @return String property name
     */
    public String getName() {
        return this.propertyName.getName();
    }
    
    /**
     * Namespace accessor.
     *
     * @return String definition
     */
    public String getNamespace() {
        return this.propertyName.getNamespace();
    }
    
    /**
     * Returnd the property name object.
     */
    public PropertyName getPropertyName() {
        return this.propertyName;
    }
    
    /**
     * Value accessor.
     *
     * @return Object value
     */
    public Object getValue() {
        return value;
    }
    
    
    /**
     * Type accessor.
     *
     * @return String type
     */
    public String getType() {
        return type;
    }
    
    /**
     * Kind accessor.
     *
     * @return the property kind (dead, live, protected, computed)
     */
    public Kind getKind() {
        return kind;
    }
    
    
    /**
     * Kind mutator.
     *
     * @deprecated use apropriate ctor 
     * @param kind the kind
     */
    public void setKind( Kind kind ) {
        if( kind == null ) {
            this.kind = Kind.DEAD;
        } else {
            this.kind = kind;
        }
    }
    
    
    /**
     * Add permission.
     *
     * @param permission Permission
     */
    public void addPermission(NodePermission permission) {
        if (this.permissions == null) {
            this.permissions = new Vector();
        }
        permissions.addElement(permission);
    }
    
    
    /**
     * Remove permission.
     *
     * @param permission Permission to remove
     */
    public void removePermission(NodePermission permission) {
        if (this.permissions != null) {
            permissions.removeElement(permission);
        }
    }
    
    
    /**
     * Enumerate permissions.
     *
     * @return Enumeration permissions
     */
    public Enumeration enumeratePermissions() {
        if (this.permissions != null) {
            return permissions.elements();
        } else {
            return EmptyEnumeration.INSTANCE;
        }
    }
    
    
    // --------------------------------------------------------- Object Methods
    
    
    /**
     * Clone.
     *
     * @return Object clone
     */
    NodeProperty cloneObject() {
        NodeProperty result = null;
        try {
            result = (NodeProperty) super.clone();
        } catch(CloneNotSupportedException e) {
        }
        return result;
    }
    
    
    /**
     * Hash Code.
     *
     * @return int Hash code value
     */
    public int hashCode() {
        // FIXME this does not correspont to equals
        return getName().hashCode();
    }
    
    
    /**
     * String representation of the permission.
     * <p/>
     * Format : ObjectUri-SubjectUri-ActionUri-InheritanceFlag
     *
     * @return String String representation
     */
    public String toString() {
        return new StringBuffer(128)
            .append(this.propertyName)
            .append('=')
            .append(this.value)
            .toString();
    }
    
    
    /**
     * Equals.
     *
     * @param obj Object to test
     * @return boolean True if the two object are equal :
     * <li>obj is of type NodeProperty and is not null</li>
     * <li>The property names are equal</li>
     */
    public boolean equals(Object obj) {
        if (this == obj) return true;
        // FIXME this does not consider value !!??
        if (obj instanceof NodeProperty) {
            NodeProperty that = (NodeProperty) obj;
            return this.propertyName.equals(that.propertyName);
        }

        return false;
    }
    
    
    /**
     * Validate.
     */
    public void validate() {
    }
    
    /**
     * The kind of a property: dead, live, protected, computed
     */
    public static class Kind implements Serializable {
        
        private static int
            DEAD_ID      = 0,
            LIVE_ID      = 1,
            PROTECTED_ID = 2,
            COMPUTED_ID  = 3;
        
        /** The discrete values */
        public static Kind
            DEAD         = new Kind( DEAD_ID ),
            LIVE         = new Kind( LIVE_ID ),
            PROTECTED    = new Kind( PROTECTED_ID ),
            COMPUTED     = new Kind( COMPUTED_ID );
        
        private int id = 0;
        
        /**
         * Private constructor
         */
        private Kind( int id ) {
            this.id = id;
        }

        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (object instanceof Kind) {
                return ((Kind) object).id == id;
            }
            return false;
        }
    }
    
    /**
     * The usage of this class avoids the creation of mutliple Namespace objects
     * with the same URI but different prefix. Just use it as a replacement
     * for the <code>org.jdom.Namespace</code>.
     * It also predefines Namespace objects for the <code>DAV:</code> and
     * the <code>http://jakarta.apache.org/slide/</code> namespace
     * (with an appropriate prefix).
     */
    public static class NamespaceCache {
        
        /**
         * String constant for <code>S</code>.
         */
        public final static String SLIDE_PREFIX = "S";
        
        /**
         * String constant for <code>http://jakarta.apache.org/slide/</code>.
         */
        public final static String SLIDE_URI = NodeProperty.SLIDE_NAMESPACE;
        
        /**
         * Namespace with {@link #SLIDE_PREFIX SLIDE_PREFIX} and
         * {@link #SLIDE_URI SLIDE_URI}.
         */
        public final static Namespace SLIDE_NAMESPACE = getNamespace(SLIDE_PREFIX, SLIDE_URI);;
        
        /**
         * String constant for <code>D</code>.
         */
        public final static String DEFAULT_PREFIX = "D";
        
        /**
         * String constant for <code>DAV:</code>.
         */
        public final static String DEFAULT_URI = NodeProperty.DEFAULT_NAMESPACE;
        
        /**
         * Namespace with {@link #DEFAULT_PREFIX DEFAULT_PREFIX} and
         * {@link #DEFAULT_URI DEFAULT_URI}.
         */
        public final static Namespace DEFAULT_NAMESPACE = getNamespace(DEFAULT_PREFIX, DEFAULT_URI);;
        
        /**
         * Maps the namespace' URI to the Namespace object.
         */
        protected static Map namespaceMap;
        
        
        /**
         * Returns the Namespace for the given <code>uri</code>.
         * If there is already an entry in the cache for this URI,
         * this Namespace will be returned.
         * Otherwise a new Namespace with the prefix <code>""</code>
         * (default namespace) will be created and put into the cache.
         *
         * @param      uri  the URI for which to return the Namespace.
         *
         * @return     the Namespace for the given URI.
         */
        public static Namespace getNamespace(String uri) {
            return getNamespace("", uri);
        }
        
        /**
         * Returns the Namespace for the given <code>prefix</code> and
         * <code>uri</code>.
         * If there is already an entry in the cache for this URI,
         * this Namespace will be returned.
         * Otherwise a new Namespace with the given <code>prefix</code>
         * and <code>uri</code> will be created and put into the cache.
         *
         * @param      prefix  the prefix for which to return the Namespace.
         * @param      uri     the URI for which to return the Namespace.
         *
         * @return     the Namespace for the given URI.
         */
        public static Namespace getNamespace(String prefix, String uri) {
            
            Namespace namespace = (Namespace)getNamespaceMap().get(uri);
            if (namespace == null) {
                namespace = Namespace.getNamespace(prefix, uri);
                getNamespaceMap().put(namespace.getURI(), namespace);
            }
            return namespace;
        }
        
        /**
         * Returns the {@link #namespaceMap namespaceMap}.
         *
         * @return     the {@link #namespaceMap namespaceMap}.
         */
        protected static Map getNamespaceMap() {
            if (namespaceMap == null) {
                namespaceMap = new HashMap();
            }
            return namespaceMap;
        }
    }
    
}
