# DirNode.java

    /*
    ** Module   :DirNode.java
    ** Abstract :Directory node data container.
    **
    ** Copyright (c) 2005-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
    ** 001 SIY 20050508  @21193  Created initial version (extracted from
    **                           RamNode)
    ** 002 NVS 20060426  @25725  Added protection against wrong class names
    ** 003 SIY 20090712  @43139  Fixed method name. Inferred generics, refactoring.
    **                           Added hasChild() method.
    ** 004 SIY 20091007  @44127  Replaced HashMap with LinkedHashMap in attributes
    **                           and child nodes storage. This significantly
    **                           reduces randomness of order of nodes/attributes
    **                           in files produced by XmlRemapper.
    ** 005 SP  20250416          LinkedHashMap was replaced with CaseInsensitiveLinkedHashMap in attributes
    **                           and child nodes storage. Removed toLowerCase() on node names.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.directory;

    import com.goldencode.util.*;

    import java.util.*;

    /**
     * This class is used to hold node data - definition, attributes and list of
     * names of child nodes. It also implements most functionality required by
     * simple back-ends which hold data in memory. Derived classes may use
     * existing data members of this class (in particular <code>childList</code>)
     * to store child nodes, not just their names.
     *
     * @author  SIY
     * @version 1.0
     */
    class DirNode
    {
       /** Current node attributes */
       private Map<String, Attribute> attributes;

       /** List of child nodes */
       protected Map<String, Object> childList;

       /** Node name */
       private String name;

       /** Object Class represented by node */
       protected ObjectClass nodeClass;

       /**
        * Constructs an empty node of given class and name. Note that constructed
        * node may be not valid until all mandatory attributes are filled.
        *
        * @param   nodeClass
        *          Object Class of the created node.
        * @param   name
        *          Node name.
        */
       DirNode(ObjectClass nodeClass, String name)
       {
          this.nodeClass = nodeClass;
          attributes = Collections.synchronizedMap(new CaseInsensitiveLinkedHashMap<String, Attribute>());
          childList  = Collections.synchronizedMap(new CaseInsensitiveLinkedHashMap<String, Object>());

          setName(name);
       }

       /**
        * Constructs an empty node of given class name and name. Note that
        * constructed node may be not valid until all mandatory attributes are
        * filled.
        *
        * @param   className
        *          Object Class name of the created node.
        * @param   name
        *          Node name.
        */
       DirNode(String className, String name)
       {
          this(SchemaStorage.getSchema().getObjectClass(className), name);
          ObjectClass oc = SchemaStorage.getSchema().getObjectClass(className);

          if (oc == null)
          {
             throw new RuntimeException("invalid directory class name " + className);
          }
       }

       /**
        * Constructs node as copy of other node.
        *
        * @param   other
        *          <code>DirNode</code> instance to copy data from.
        */
       DirNode(DirNode other)
       {
          this(other.getNodeClass(), other.getName());

          String[] child = other.getChildNames();

          for (int i = 0; child != null && i < child.length; i++)
          {
             childList.put(child[i], child[i]);
          }

          replaceAttributes(other.getAttributes());
       }

       /**
        * Add attribute to the node. Replace existing attribute if it exists.
        *
        * @param   attribute
        *          Attribute instance to add.
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean addAttribute(Attribute attribute)
       {
          if (attribute == null)
          {
             return false;
          }
          if (!attribute.isValid())
          {
             return false;
          }

          AttributeDefinition def;
          def = nodeClass.getAttributeDefinition(attribute.getName());
          if (def == null || !def.isCompatible(attribute.getDefinition()))
          {
             return false;
          }

          return true;
       }

       /**
        * Add array of attributes to the node. Replace existing if they exist and
        * remove existing attributes if they are not in array.
        *
        * @param   attrList
        *          List of new node attributes.
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean replaceAttributes(Attribute[] attrList)
       {
          if (attrList == null)
          {
             return false;
          }

          for (int i = 0; i < attrList.length; i++)
          {
             if (attrList[i] == null || !attrList[i].isValid())
             {
                return false;
             }

             AttributeDefinition def;
             def = nodeClass.getAttributeDefinition(attrList[i].getName());
             if (def == null || !def.isCompatible(attrList[i].getDefinition()))
             {
                return false;
             }
          }

          attributes.clear();
          for (int i = 0; i < attrList.length; i++)
          {
             addAttributeInt(new Attribute(attrList[i]));
          }

          return true;
       }

       /**
        * Add new attribute value.
        *
        * @param   attrName
        *          Attribute name
        * @param   value
        *          Attribute value
        * @param   initial
        *          Allow initialisation of the immutable attributes.
        *          <code>true</code> if this is initialisation mode.
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean addAttributeValue(String attrName, Object value, boolean initial)
       {
          if (attrName == null)
          {
             return false;
          }

          //Locate attribute definition
          AttributeDefinition def = nodeClass.getAttributeDefinition(attrName);
          if (def == null)
          {
             return false;
          }
          if (!initial && def.isImmutable())
          {
             return false;
          }

          Attribute attribute = getAttribute(attrName);
          if (attribute != null)
          {
             return attribute.addValue(value);
          }

          //Create new attribute
          attribute = new Attribute(def.genNodeAttribute(), new Object[]
             {
                value
             });
          addAttributeInt(attribute);

          return true;
       }

       /**
        * Add a new child node if this is allowed.
        *
        * @param   child
        *          Child name.
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean addChild(String child)
       {
          if (child == null)
          {
             return false;
          }
          if (nodeClass.isClassLeaf())
          {
             return false;
          }
          if (childList.get(child) != null) //Node exists
          {
             return false;
          }
          childList.put(child, child);
          return true;
       }

       /**
        * Add new attribute value using <code>String</code> as a data source.
        *
        * @param   attrName
        *          Attribute name
        * @param   value
        *          Attribute value
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean addStringAttributeValue(String attrName, String value)
       {
          if (attrName == null || value == null)
          {
             return false;
          }

          //Locate attribute definition
          AttributeDefinition def = nodeClass.getAttributeDefinition(attrName);
          if (def == null)
          {
             return false;
          }
          if (def.isImmutable())
          {
             return false;
          }
          Attribute attribute = getAttribute(attrName);
          if (attribute == null)
          {
             attribute = new Attribute(def.genNodeAttribute(), null);
             if (attribute.addStringValue(value))
             {
                addAttributeInt(attribute);
                return true;
             }
             return false;
          }
          return attribute.addStringValue(value);
       }

       /**
        * Get existing attribute or generate new empty instance of
        * <code>Attribute</code>.
        *
        * @param   attrName
        *          Attribute name.
        *
        * @return  Reference to existing or created attribute instance.
        */
       Attribute genAttribute(String attrName)
       {
          Attribute attribute = getAttribute(attrName);
          if (attribute != null)
          {
             return attribute;
          }

          AttributeDefinition[] list = nodeClass.getClassDefinition();
          if (list == null)
          {
             return null;
          }
          for (int i = 0; i < list.length; i++)
          {
             if (list[i].getName().equals(attrName))
             {
                return new Attribute(list[i].genNodeAttribute(), null);
             }
          }
          return null;
       }

       /**
        * Get an attribute with specified name.
        *
        * @param   name
        *          Name of the attribute to return.
        *
        * @return  Reference to attribute is such an attribute exists or
        *          <code>null</code> otherwise.
        */
       Attribute getAttribute(String name)
       {
          if (name == null)
          {
             return null;
          }
          return attributes.get(name);
       }

       /**
        * Get a list of all attributes.
        *
        * @return  An array of all node attributes.
        */
       Attribute[] getAttributes()
       {
          return attributes.values().toArray(new Attribute[0]);
       }

       /**
        * Get number of children.
        *
        * @return  Number of child nodes.
        */
       int getChildCount()
       {
          return childList.size();
       }

       /**
        * Get a list of all child node names.
        *
        * @return  An array of all child names.
        */
       String[] getChildNames()
       {
          return childList.values().toArray(new String[0]);
       }

       /**
        * Get node name.
        *
        * @return  Node name.
        */
       String getName()
       {
          return name;
       }

       /**
        * Get node Object Class.
        *
        * @return  ObjectClass instance assigned to node.
        */
       ObjectClass getNodeClass()
       {
          return nodeClass;
       }

       /**
        * Check if child node with specified name exists.
        *
        * @param    name
        *           Child node name.
        *
        * @return   <code>true</code> if child with specified name exists.
        */
       boolean hasChild(String name)
       {
          return childList.containsKey(name);
       }

       /**
        * Check if node is of specified Object Class.
        *
        * @param   className
        *          An object class name to compare with.
        *
        * @return  <code>true</code> if node is of specified object class and
        *          <code>false</code> otherwise.
        */
       boolean isA(String className)
       {
          return nodeClass.getName().equals(className);
       }

       /**
        * Verify validness of the node. Node is considered valid if all mandatory
        * attributes are set.
        *
        * @return  <code>true</code> if node is valid.
        */
       boolean isValid()
       {
          AttributeDefinition[] list = nodeClass.getClassDefinition();
          if (list == null && attributes.size() == 0)
          {
             return true;
          }
          if (list == null)
          {
             return true;
          }
          for (int i = 0; i < list.length; i++)
          {
             Attribute attribute = getAttribute(list[i].getName());
             if (attribute == null)
             {
                if (!list[i].isMandatory())
                {
                   continue;
                }
                //Attribute is mandatory but it is missing
                return false;
             }
             if (!attribute.isValid())
             {
                return false;
             }
          }
          return true;
       }

       /**
        * Remove attribute with specified name.
        *
        * @param   name
        *          Name of the attribute to remove.
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean removeAttribute(String name)
       {
          if (name == null) //No such attribute
          {
             return false;
          }
          Attribute attr = getAttribute(name);
          if (attr == null) //No such attribute
          {
             return false;
          }
          if (attr.getDefinition().isMandatory())
          {
             return false;
          }
          attributes.remove(name);
          return true;
       }

       /**
        * Add or replace attribute with specified one.
        *
        * @param   attribute
        *          An attribute to add or replace existing.
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean setAttribute(Attribute attribute)
       {
          if (attribute == null)
          {
             return false;
          }
          if (getAttribute(attribute.getName()) == null)
          {
             //There is no such attribute so we need to check if such
             //an attribute can be added.
             AttributeDefinition def;
             def = nodeClass.getAttributeDefinition(attribute.getName());
             if (def == null || !def.isCompatible(attribute.getDefinition()))
             {
                return false;
             }
          }
          addAttributeInt(attribute);
          return true;
       }

       /**
        * Set new attribute value.
        *
        * @param   attrName
        *          Attribute name
        * @param   value
        *          New value for the attribute
        * @param   ndx
        *          Index of the value
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean setAttributeValue(String attrName, Object value, int ndx)
       {
          if (attrName == null)
          {
             return false;
          }
          Attribute attribute = getAttribute(attrName);
          if (attribute == null)
          {
             return false;
          }
          //Locate attribute definition
          AttributeDefinition def = nodeClass.getAttributeDefinition(attrName);
          if (def == null)
          {
             return false;
          }
          if (def.isImmutable())
          {
             return false;
          }
          return attribute.setValue(ndx, value);
       }

       /**
        * Rename node.
        *
        * @param   name
        *          New name for the node.
        */
       void setName(String name)
       {
          this.name = new String(name);
       }

       /**
        * Check if object class of the node does not allow adding child nodes.
        *
        * @return  <code>true</code> if object class is leaf.
        */
       boolean isLeaf()
       {
          return nodeClass.isClassLeaf();
       }

       /**
        * Remove child with specified name.
        *
        * @param   child
        *          Name of the child to remove.
        *
        * @return  <code>true</code> if operation was successful.
        */
       boolean removeChild(String child)
       {
          if (child == null || childList.get(child) == null) //No such child
          {
             return false;
          }
          childList.remove(child);
          return true;
       }

       /**
        * Convenience method which adds attribute into the set of node attributes.
        *
        * @param   attribute
        *          Attribute to add.
        */
       private void addAttributeInt(Attribute attribute)
       {
          attributes.put(attribute.getName(), attribute);
       }
    }