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);
      }
   }
}