# RamRemapper.java

    /*
    ** Module   : RamRemapper.java
    ** Abstract : Memory-only back-end implementation.
    **
    ** Copyright (c) 2005-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
    ** 001 SIY 20050406   @21026 Created initial version by extracting
    **                           common code from the XmlRemapper.
    ** 002 SIY 20050506   @21200 Moved splitId() to IdUtils, updated documentation.
    ** 003 SIY 20050524   @21262 Fixed renaming nodes.
    ** 004 SIY 20090514   @42182 Added refresh() method.
    ** 005 SIY 20090712   @43140 Used isLeaf() method instead of chained call.
    ** 006 NVS 20090928   @44046 Logging exceptions instead of printing stack trace
    **                           to stdout.
    ** 007 GES 20101119          Better exception logging to provide specific detail
    **                           on the directory path to a syntax failure.
    ** 008 TJD 20220504          Java 11 compatibility minor changes
    ** 009 SP  20250417          Code formatting and javadoc fixes.
    */

    /*
    ** 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;

    /**
     * Memory-only back-end implementation. The memory-only mode is used by
     * various other package classes to represent P2J Directory in the memory and
     * other purposes.
     *
     * @author  SIY
     * @version 1.0
     */
    class RamRemapper
    implements Remapper
    {
       /** Object which is used synchronize access to tree root */
       protected final Object mutex = new Object();

       /** Root node for the in memory representation */
       protected RamNode root = null;

       /** Track bind/unbind calls */
       private boolean bound = false;

       /** Schema container */
       private SchemaStorage schema;

       /**
        * Construct an instance of the XmlRemapper for a given file.
        */
       RamRemapper()
       {
          schema = SchemaStorage.getSchema();

          setRoot(null);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNode(String nodeId, String nodeClass, String[] names,
                              Object[] values)
       {
          if (!isBound())
          {
             return false;
          }

          String[] path = IdUtils.splitId(nodeId);
          if (path == null) //Invalid path or something
          {
             return false;
          }

          synchronized (mutex)
          {
             RamNode parent = locateNode(path[0]);
             if (parent == null)
             {
                return false;
             }
             //This check now is performed in the DirectoryService class
             //if (parent.getNodeClass().isClassLeaf())
             //   return false;
             if (parent.getChild(path[1]) != null)  //Child already exists
             {
                return false;
             }

             ObjectClass objClass = getObjClass(nodeClass);
             if (objClass == null)
             {
                return false;
             }

             RamNode node = new RamNode(objClass, path[1]);

             if (names != null && values != null)
             {
                if (names.length != values.length)
                {
                   return false;
                }
                for (int i = 0; i < names.length; i++)
                {
                   boolean rc = node.addAttributeValue(names[i], values[i], true);
                   if (!rc)
                   {
                      return false;
                   }
                }
             }
             if (!node.isValid())
             {
                return false;
             }
             return addNodeExt(parent, node);
          }
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeBitField(String nodeId, String name, BitField value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeBitSelector(String nodeId, String name,
                                         BitSelector value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeBoolean(String nodeId, String name, boolean value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeByteArray(String nodeId, String name, byte[] value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeDate(String nodeId, String name, DateValue value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeDouble(String nodeId, String name, double value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeInteger(String nodeId, String name, int value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeString(String nodeId, String name, String value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean addNodeTime(String nodeId, String name, TimeValue value)
       {
          return addNodeValue(nodeId, name, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean bind()
       {
          try
          {
             synchronized (mutex)
             {
                if (isBound())
                {
                   return false;
                }
                load();
                bound = true;
             }
          }
          catch (Exception e)
          {
             DirectoryService.logError(e.getMessage(), e);
             setRoot(null);
             return false;
          }
          return true;
       }

       /**
        * {@inheritDoc}
        */
       public boolean deleteNode(String nodeId)
       {
          if (!isBound())
          {
             return false;
          }
          String[] path = IdUtils.splitId(nodeId);
          if (path == null) //Invalid path or something
          {
             return false;
          }
          synchronized (mutex)
          {
             //RamNode node = locateNode(nodeId);
             RamNode parent = locateNode(path[0]);
             if (parent == null)
             {
                return false;
             }
             RamNode node = parent.getChild(path[1]);
             if (node == null)
             {
                return false;
             }
             if (node.getChildCount() > 0)
             {
                return false;
             }
             return deleteNodeExt(parent, path[1]);
          }
       }

       /**
        * {@inheritDoc}
        */
       public boolean deleteNodeAttribute(String nodeId, String name)
       {
          if (!isBound())
          {
             return false;
          }
          synchronized (mutex)
          {
             RamNode node = locateNode(nodeId);
             if (node == null)
             {
                return false;
             }
             return deleteNodeAttributeExt(node, name);
          }
       }

       /**
        * {@inheritDoc}
        */
       public boolean deleteNodeAttributeValue(String nodeId, String name,
                                               int index)
       {
          if (!isBound())
          {
             return false;
          }
          synchronized (mutex)
          {
             RamNode node = locateNode(nodeId);
             if (node == null)
             {
                return false;
             }
             return deleteNodeAttributeValueExt(node, name, index);
          }
       }

       /**
        * {@inheritDoc}
        */
       public AttributeDefinition[] enumerateNodeAttributes(String nodeId)
       {
          if (!isBound())
          {
             return null;
          }
          synchronized (mutex)
          {
             RamNode node = locateNode(nodeId);
             if (node == null)
             {
                return null;
             }
             AttributeDefinition[] src = node.getNodeClass().getClassDefinition();
             if (src == null)
             {
                return src;
             }
             //Create a copy of the array but not elements.
             //There is no need to copy elements since
             //AttributeDefinition is "read only".
             AttributeDefinition[] res = new AttributeDefinition[src.length];
             for (int i = 0; i < src.length; i++)
             {
                res[i] = src[i];
             }
             return res;
          }
       }

       /**
        * {@inheritDoc}
        */
       public String[] enumerateNodes(String nodeId)
       {
          if (!isBound())
          {
             return null;
          }
          synchronized (mutex)
          {
             RamNode node = locateNode0(nodeId);
             if (node == null)
             {
                return null;
             }
             RamNode[] childList = node.getChildList();
             if (childList == null)
             {
                return null;
             }
             String[] ret = new String[childList.length];
             for (int i = 0; i < ret.length; i++)
             {
                ret[i] = new String(childList[i].getName());
             }
             return ret;
          }
       }

       /**
        * {@inheritDoc}
        */
       public AttributeDefinition[] getClassDefinition(String className)
       {
          ObjectClass objClass = getObjClass(className);
          if (objClass == null)
          {
             return null;
          }
          return objClass.getClassDefinition();
       }

       /**
        * {@inheritDoc}
        */
       public String[] getClassNames()
       {
          return schema.getClassNames();
       }

       /**
        * {@inheritDoc}
        */
       public BitField getNodeBitField(String nodeId, String name, int index)
       {
          return (BitField)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public BitSelector getNodeBitSelector(String nodeId, String name, int index)
       {
          return (BitSelector)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public Boolean getNodeBoolean(String nodeId, String name, int index)
       {
          return (Boolean)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public byte[] getNodeByteArray(String nodeId, String name, int index)
       {
          return (byte[])getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public String getNodeClassName(String nodeId)
       {
          if (!isBound())
          {
             return null;
          }
          synchronized (mutex)
          {
             RamNode node = locateNode(nodeId);
             if (node == null)
             {
                return null;
             }
             return node.getNodeClass().getName();
          }
       }

       /**
        * {@inheritDoc}
        */
       public DateValue getNodeDate(String nodeId, String name, int index)
       {
          return (DateValue)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public Double getNodeDouble(String nodeId, String name, int index)
       {
          return (Double)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public Integer getNodeInteger(String nodeId, String name, int index)
       {
          return (Integer)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public String getNodeString(String nodeId, String name, int index)
       {
          return (String)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public TimeValue getNodeTime(String nodeId, String name, int index)
       {
          return (TimeValue)getNodeValue(nodeId, name, index);
       }

       /**
        * {@inheritDoc}
        */
       public boolean isClassImmutable(String className)
       {
          ObjectClass objClass = getObjClass(className);
          if (objClass == null)
          {
             return true;
          }
          return objClass.isClassImmutable();
       }

       /**
        * {@inheritDoc}
        */
       public boolean isClassLeaf(String className)
       {
          ObjectClass objClass = getObjClass(className);
          if (objClass == null)
          {
             return false;
          }
          return objClass.isClassLeaf();
       }

       /**
        * {@inheritDoc}
        */
       public boolean moveNode(String nodeId, String newId)
       {
          if (!isBound())
          {
             return false;
          }
          String[] path = IdUtils.splitId(nodeId);
          String[] newPath = IdUtils.splitId(newId);
          if (path == null || newPath == null)
          {
             return false;
          }
          synchronized (mutex)
          {
             RamNode parent = locateNode(path[0]);
             RamNode newParent = locateNode(newPath[0]);
             if (parent == null || newParent == null)
             {
                return false;
             }
             if(!path[0].equals(newPath[0]))
             {
                if (parent.getChild(path[1]) == null ||
                newParent.getChild(path[1]) != null)
                {
                   return false;
                }
             }
             if (newParent.isLeaf())
             {
                return false;
             }
             return moveNodeExt(parent, path[1], newParent, newPath[1]);
          }
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeBitField(String nodeId, String name, int index,
                                      BitField value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeBitSelector(String nodeId, String name, int index,
                                         BitSelector value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeBoolean(String nodeId, String name, int index,
                                     boolean value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeByteArray(String nodeId, String name, int index,
                                       byte[] value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeDate(String nodeId, String name, int index,
                                  DateValue value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeDouble(String nodeId, String name, int index,
                                    double value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeInteger(String nodeId, String name, int index,
                                     int value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeString(String nodeId, String name, int index,
                                    String value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean setNodeTime(String nodeId, String name, int index,
                                  TimeValue value)
       {
          return setNodeValue(nodeId, name, index, value);
       }

       /**
        * {@inheritDoc}
        */
       public boolean unbind()
       {
          try
          {
             synchronized (mutex)
             {
                if (!isBound())
                {
                   return false;
                }
                //No need to save it here.
                //save();
                bound = false;
             }
          }
          catch (Exception e)
          {
             DirectoryService.logError(e.getMessage());
             return false;
          }
          return true;
       }

       /**
        * {@inheritDoc}
        */
       public boolean update()
       {
          try
          {
             save();
             return true;
          }
          catch (Exception e)
          {
             DirectoryService.logError(e.getMessage());
          }
          return false;
       }

       /**
        * Generic implementation.
        *
        * @see   com.goldencode.p2j.directory.Remapper#refresh()
        */
       public boolean refresh()
       {
          if (!isBound())
          {
             return true;
          }
          RamNode root = getRoot();
          try
          {
             setRoot(null);
             load();
          }
          catch (Exception e)
          {
             DirectoryService.logError(e.getMessage());
             setRoot(root);
             return false;
          }
          // by default we do nothing
          return true;
       }

       /**
        * This method is invoked by <code>addAttributeValue()</code> when most
        * checks are passed and it is about to add new value to the attribute.
        * Derived classes should override it if they need to handle this
        * operation. The main reason of this method is that it is invoked inside
        * synchronisation block and therefore derived class does not need to worry
        * about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        *
        * @param   node
        *          A node from which attribute value is requested.
        * @param   name
        *          Name of the attribute.
        * @param   val
        *          New value which will be added to the attribute.
        *
        * @return  <code>true</code> is operation is successful and
        *          <code>false</code> otherwise.
        */
       protected boolean addAttributeValueExt(RamNode node, String name,
                                              Object val)
       {
          return node.addAttributeValue(name, val, false);
       }

       /**
        * This method is invoked by <code>addNode()</code> when all checks are
        * passed and it is about to add new node to the tree. Derived classes
        * should override it if they need to handle adding node somehow. The main
        * reason of this method is that it is invoked inside synchronisation block
        * and therefore derived class does not need to worry about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        *
        * @param   parent
        *          A node to which <code>newNode</code> will be added.
        * @param   newNode
        *          New node which need to be added to <code>parent</code>.
        *
        * @return  <code>true</code> if operation was successful and
        *          <code>false</code> otherwise.
        */
       protected boolean addNodeExt(RamNode parent, RamNode newNode)
       {
          return parent.addChild(newNode);
       }

       /**
        * This method is invoked by <code>deleteNodeAttribute()</code> when most
        * checks are passed and it is about to delete attribute. Derived classes
        * should override it if they need to handle this operation. The main
        * reason of this method is that it is invoked inside synchronisation block
        * and therefore derived class does not need to worry about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        *
        * @param   node
        *          A node from which attribute value is requested.
        * @param   name
        *          Name of the attribute.
        *
        * @return  <code>true</code> if operation was successful and
        *          <code>false</code> otherwise.
        */
       protected boolean deleteNodeAttributeExt(RamNode node, String name)
       {
          return node.removeAttribute(name);
       }

       /**
        * This method is invoked by <code>deleteNodeAttribute()</code> when most
        * checks are passed and it is about to delete attribute value. Derived
        * classes should override it if they need to handle this operation. The
        * main reason of this method is that it is invoked inside synchronisation
        * block and therefore derived class does not need to worry about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        * <p>
        * Note that according to specification attribute must be deleted if last
        * value is removed.
        *
        * @param   node
        *          A node from which attribute value will be deleted.
        * @param   name
        *          Name of the attribute.
        * @param   index
        *          Attribute value index.
        *
        * @return  <code>true</code> is operation is successful and
        *          <code>false</code> otherwise.
        */
       protected boolean deleteNodeAttributeValueExt(RamNode node, String name,
                                                     int index)
       {
          Attribute attr = node.getAttribute(name);
          if (attr == null)
          {
             return false;
          }
          if (!attr.deleteValue(index))
          {
             return false;
          }
          if (attr.getCount() == 0)
          {
             return node.removeAttribute(name);
          }
          return true;
       }

       /**
        * This method is invoked by <code>deleteNode()</code> when all checks are
        * passed and it is about to remove node from the tree. Derived classes
        * should override it if they need to handle removing of the node somehow. The main
        * reason of this method is that it is invoked inside synchronisation block
        * and therefore derived class does not need to worry about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        *
        * @param   parent
        *          A node to which <code>newNode</code> will be added.
        * @param   child
        *          An ID of the child node to remove.
        *
        * @return  <code>true</code> if operation was successful and
        *          <code>false</code> otherwise.
        */
       protected boolean deleteNodeExt(RamNode parent, String child)
       {
          return parent.removeChild(child);
       }

       /**
        * This method is invoked by <code>getNodeValue()</code> when most checks
        * are passed and it is about to retrieve attribute value. Derived classes
        * should override it if they need to handle this operation. The main
        * reason of this method is that it is invoked inside synchronisation block
        * and therefore derived class does not need to worry about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        *
        * @param   node
        *          A node from which attribute value is requested.
        * @param   name
        *          Name of the attribute.
        * @param   ndx
        *          Index of the attribute value.
        *
        * @return  Attribute value.
        */
       protected Object getNodeValueExt(RamNode node, String name, int ndx)
       {
          Attribute attr = node.getAttribute(name);
          if (attr == null)
          {
             return null;
          }
          return attr.getValue(ndx);
       }

       /**
        * Return ObjectClass for a given name.
        *
        * @param   name
        *          Class name to find.
        *
        * @return  ObjectClass if class with such a name exists and
        *          <code>null</code> otherwise.
        */
       protected ObjectClass getObjClass(String name)
       {
          return schema.getObjectClass(name);
       }

       /**
        * Verifies that calling thread is properly bound.
        *
        * @return  <code>true</code> if thread is properly bound.
        */
       protected boolean isBound()
       {
          return bound;
       }

       /**
        * Load tree from external storage.
        *
        * @throws  Exception
        *          In case of various errors. The use of Exception here is
        *          intentional: child classes must be allowed throw other
        *          exceptions without changing method signature.
        */
       protected void load()
       throws Exception
       {
       }

       /**
        * Locate node for given nodeId. If there is no such node then
        * <code>null</code> is returned.
        *
        * @param   id
        *          Node id to locate.
        * @return  Reference to node.
        */
       protected RamNode locateNode(String id)
       {
          return locateNode0(id);
       }

       /**
        * This method is invoked by <code>moveNode()</code> when most checks are
        * passed and it is about to move node to the new location. Derived classes
        * should override it if they need to handle this operation. The main
        * reason of this method is that it is invoked inside synchronisation block
        * and therefore derived class does not need to worry about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        * <p>
        * Note that derived class can check if parent and newParent parameters
        * point to the same node. In many cases such a situation can be performed
        * in more efficient way.
        *
        * @param   parent
        *          A parent node of the node which is about to be moved to new
        *          location.
        * @param   name
        *          Name of the node which will be moved.
        * @param   newParent
        *          A node which will be new parent node if operation will be
        *          successful.
        * @param   newName
        *          New name of the node.
        *
        * @return  <code>true</code> is operation is successful and
        *          <code>false</code> otherwise.
        */
       protected boolean moveNodeExt(RamNode parent, String name,
                                     RamNode newParent, String newName)
       {
          RamNode node = parent.getChild(name);
          if (parent == newParent && name.equals(newName))
          {
             return true;
          }
          parent.removeChild(name);
          node.setName(newName);
          newParent.addChild(node);
          return true;
       }

       /**
        * Save current tree into external storage.
        *
        * @throws  Exception
        *          In case of various errors. The use of Exception here is
        *          intentional: child classes must be allowed throw other
        *          exceptions without changing method signature.
        */
       protected void save()
       throws Exception
       {
       }

       /**
        * This method is invoked by <code>setNodeValue()</code> when most checks
        * are passed and it is about to change attribute value. Derived classes
        * should override it if they need to handle this operation. The main
        * reason of this method is that it is invoked inside synchronisation block
        * and therefore derived class does not need to worry about this.
        * <p>
        * Note that derived class must call this method before successful exit.
        *
        * @param   node
        *          A node which contains the attribute to change.
        * @param   name
        *          Attribute name.
        * @param   index
        *          Index of the value to retrieve.
        * @param   val
        *          New value for the attribute variable.
        *
        * @return  <code>true</code> is operation is successful and
        *          <code>false</code> otherwise.
        */
       protected boolean setNodeValueExt(RamNode node, String name, Object val,
                                         int index)
       {
          return node.setAttributeValue(name, val, index);
       }

       /**
        * Return root of the three.
        *
        * @return  Reference to root node.
        */
       RamNode getRoot()
       {
          return root;
       }

       /**
        * Set tree root.
        *
        * @param   node
        *          The root node of the tree.
        */
       void setRoot(RamNode node)
       {
          root = node;

          if (root == null)
             root = new RamNode(getObjClass("container"), "");
       }

       /**
        * Add new value to the attribute.
        *
        * @param   id
        *          Node identifier.
        * @param   name
        *          Attribute name.
        * @param   val
        *          New value for the attribute variable.
        *
        * @return  <code>true</code> is operation is successful and
        *          <code>false</code> otherwise.
        */
       private boolean addNodeValue(String id, String name, Object val)
       {
          if (!isBound())
          {
             return false;
          }
          synchronized (mutex)
          {
             RamNode node = locateNode(id);
             if (node == null)
             {
                return false;
             }
             return addAttributeValueExt(node, name, val);
          }
       }

       /**
        * Return value of specified variable of node attribute.
        *
        * @param   id
        *          Node identifier.
        * @param   name
        *          Attribute name.
        * @param   ndx
        *          Index of the value to retrieve.
        *
        * @return  Value of the attribute for the specified attribute name and
        *          index or <code>null</code>.
        */
       private Object getNodeValue(String id, String name, int ndx)
       {
          if (!isBound())
          {
             return null;
          }

          synchronized (mutex)
          {
             RamNode node = locateNode(id);
             if (node == null)
             {
                return null;
             }
             return getNodeValueExt(node, name, ndx);
          }
       }

       /**
        * This is actual worker for the <code>locateNode()</code> method.
        *
        * @param   id
        *          Node id to locate.
        *
        * @return  Reference to node.
        */
       private RamNode locateNode0(String id)
       {
          id = IdUtils.normalize(id);
          if (id == null)
          {
             return null;
          }

          String[] res = id.split("/", 0);
          RamNode node = getRoot();
          for (int i = 1; i < res.length; i++)
          {
             node = node.getChild(res[i]);

             if (node == null)
             {
                return null;
             }
          }
          return node;
       }

       /**
        * Set attribute value for specified node ID, attribute name and index. If
        * wrong parameter is passed or attribute read-only then <code>false</code>
        * is returned.
        *
        * @param   id
        *          Node identifier.
        * @param   name
        *          Attribute name.
        * @param   index
        *          Index of the value to retrieve.
        * @param   val
        *          New value for the attribute variable.
        *
        * @return  <code>true</code> is operation is successful and
        *          <code>false</code> otherwise.
        */
       private boolean setNodeValue(String id, String name, int index, Object val)
       {
          if (!isBound())
          {
             return false;
          }
          synchronized (mutex)
          {
             RamNode node = locateNode(id);
             if (node == null)
             {
                return false;
             }
             return setNodeValueExt(node, name, val, index);
          }
       }
    }