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