Attribute.java
/*
** Module : Attribute.java
** Abstract : An attribute container.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description----------------------
** 001 SIY 20050216 @20011 Created initial implementation
** 002 SIY 20050308 @20351 Organized imports and reordered methods.
** 003 SIY 20050323 @20464 Fixed formatting.
** 004 SIY 20050426 @21003 Yet another pass on formatting to make it
** compliant to coding standards. Added isA().
** 005 NVS 20050426 @20889 Class implements Serializable interface.
** 006 SIY 20050514 @21184 More detailed documentation.
** 007 NVS 20070322 @32539 getValues() and getStringValue() are public
** methods now.
** 008 NVS 20090625 @42959 Protected the constructors against null arguments
** with the advice to check the directory schema file
** 009 NVS 20090924 @44026 Attributes can be instantiated without values in
** order to allow gradual value creation.
** 010 ECF 20150715 Replace StringBuffer with StringBuilder.
** 011 HC 20170612 Added support of storing com.goldencode.p2j.security.BitSet
** and java.util.Date types in the directory.
** 012 VVT 20200203 The getValue() does not use NPE just for test anymore.
** Which makes debugging a less annoying.
** 013 IAS 20200914 Re-work (de)serialization
** TJD 20220504 Upgrade do Java 11
** 014 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 015 SP 20250416 Code formatting 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;
import static com.goldencode.util.NativeTypeSerializer.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.BitSet;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
/**
* An attribute container class.
* <p>
* This class contains attribute values and attribute definition and is used
* to transfer or hold particular attribute of particular node. Using
* attribute definition it can verify current attribute content for the
* validness. It also checks new attribute values for correct type to ensure
* that attribute contains only values of specified type.
*
* @author SIY
* @version 1.0
*/
public class Attribute
implements Externalizable
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(Attribute.class);
/** Attribute definition details */
private NodeAttribute definition;
/** Storage for the attribute values */
private Vector<Object> values;
/**
* Construct an attribute as copy of other attribute
*
* @param other
* An instance to get information from.
*/
public Attribute(Attribute other)
{
// protection
if (other == null)
{
throw new IllegalArgumentException(
"Source attribute must be provided");
}
this.definition = new NodeAttribute(other.definition);
this.values = (Vector<Object>) other.values.clone();
}
/**
* Construct an attribute and assign a set of values to the attribute. Note
* that if definition contains mandatory attributes but no values are
* provided for the attribute then attribute will be invalid. Existence of
* such an attributes is allowed because in some cases attribute is built
* in several steps.
*
* @param definition
* Attribute definition.
* @param list
* An array of values which will be assigned to the attribute.
*/
public Attribute(NodeAttribute definition, Object[] list)
{
// protection
if (definition == null)
{
throw new IllegalArgumentException("Unknown attribute definition. Check the directory schema.");
}
this.definition = new NodeAttribute(definition);
this.values = new Vector<>();
if (list != null)
{
for (int i = 0; i < list.length; i++)
{
if (list[i] == null)
{
continue;
}
boolean rc = addValue(list[i]);
if (!rc)
{
throw new IllegalArgumentException("Attribute value is incorrect");
}
}
}
}
/**
* Add an value to the attribute. If attribute supports multiple values
* then parameter is added at the end of the list of values. If attribute
* does not support multiple values and there is no value assigned to the
* attribute then provided parameter is assigned to attribute value. If
* parameter is of incorrect type or attribute does not support multiple
* values and single value is already assigned then parameter is ignored
* and <code>false</code> is returned.
*
* @param value
* A value to add.
* @return result of the operation -<code>true</code> if success and
* <code>false</code> if error occurred.
*/
public boolean addValue(Object value)
{
if (value == null)
{
return false;
}
value = convertValue(value);
if (!checkType(value))
{
return false;
}
if (definition.isMultiple() || values.size() == 0)
{
try
{
values.add(value);
definition.setCount(values.size());
return true;
}
catch (Exception e)
{
LOG.log(Level.FINE, "", e);
}
}
return false;
}
/**
* Delete attribute value at specified index.
*
* @param index
* An index of variable to remove.
* @return result of the operation -<code>true</code> if success and
* <code>false</code> if error occurred.
*/
public boolean deleteValue(int index)
{
if (getCount() == 1 && index == 0 && definition.isMandatory())
{
//Do not allow removing last mandatory attribute
return false;
}
//OK, it is safe to try to delete value
try
{
values.removeElementAt(index);
definition.setCount(values.size());
return true;
}
catch (Exception e)
{
}
return false;
}
/**
* Get number of values assigned to the attributed.
*
* @return number of elements in the list of values.
*/
public int getCount()
{
return values.size();
}
/**
* Provide access to the NodeAttribute definition.
*
* @return a definition used by this attribute.
*/
public NodeAttribute getDefinition()
{
return definition;
}
/**
* Simplify access to Object Class name.
*
* @return Object Class name from definition.
*/
public String getName()
{
return definition.getName();
}
/**
* Convenience method for the <code>NodeAtribute.getType()</code>.
*
* @return attribute type.
*/
public int getType()
{
return definition.getType();
}
/**
* Compare attribute type with provided type and return <code>true</code>
* if they match.
*
* @param type
* Attribute type.
*
* @return <code>true</code> if given type match attribute type.
*/
public boolean isA(int type)
{
return (definition.getType() == type);
}
/**
* Return <code>true</code> if attribute can contain multiple values.
*
* @return <code>true</code> if attribute can contain multiple values.
*/
public boolean isMultiple()
{
return definition.isMultiple();
}
/**
* Returns the indexed value of the attribute as a generic object, that can
* be cast into its primitive data type.
*
* @param index
* Index of the value to return. 0 - for first value (or single
* value if attribute does not support multiple values).
*
* @return value of the attribute
*/
public Object getValue(int index)
{
if(index < 0 || index >= values.size())
{
return null;
}
return values.elementAt(index);
}
/**
* Check if attribute is valid, for example attributes with mandatory flag
* set at have least one value is assigned.
*
* @return <code>true</code> if attribute is valid.
*/
public boolean isValid()
{
if (!definition.isMandatory())
{
return true;
}
return (getCount() > 0);
}
/**
* Set new value to attribute at specified index. If parameter is of
* incorrect type or attribute does not support multiple values or there is
* no assigned value at specified index then parameter is ignored and
* <code>false</code> is returned.
*
* @param index
* An index of variable to set.
* @param value
* A value to set.
*
* @return result of the operation -<code>true</code> if success and
* <code>false</code> if error occurred.
*/
public boolean setValue(int index, Object value)
{
if (value == null)
{
return false;
}
value = convertValue(value);
if (!checkType(value))
{
return false;
}
try
{
values.setElementAt(value, index);
return true;
}
catch (Exception e)
{
}
return false;
}
/**
* Export attribute in human-readable form.
*
* @see java.lang.Object#toString()
*
* @return Attribute data as human-readable <code>String</code>.
*/
public String toString()
{
Object[] vals = toArray(new Object[getCount()]);
StringBuilder res = new StringBuilder();
res.append("attribute <");
res.append(getName());
res.append("> {");
for (int i = 0; i < vals.length; i++)
{
res.append(vals[i].toString());
if (i != (vals.length - 1))
{
res.append(", ");
}
}
res.append("}");
return res.toString();
}
/**
* Add an value to the attribute. Value is restored from the String
* parameter. If parameter is of incorrect type or attribute does not
* support multiple values and single value is already assigned then
* parameter is ignored and <code>false</code> is returned.
*
* @param str
* A value to add.
*
* @return result of the operation -<code>true</code> if success and
* <code>false</code> if error occurred.
*/
boolean addStringValue(String str)
{
Object value = stringToValue(str);
if (value == null)
{
return false;
}
if (!checkType(value))
{
return false;
}
if (definition.isMultiple() || values.size() == 0)
{
try
{
values.add(value);
definition.setCount(values.size());
return true;
}
catch (Exception e)
{
}
}
return false;
}
/**
* Return attribute values as array of strings.
*
* @return An array of string representation of the attribute values.
*/
public String[] getValues()
{
String[] res = new String[values.size()];
for (int i = 0; i < res.length; i++)
{
res[i] = getStringValue(i);
}
return res;
}
/**
* Convert attribute value into string.
*
* @param index
* An index to check.
*
* @return Text representation of the attribute value.
*/
public String getStringValue(int index)
{
return getStringValue(values.elementAt(index));
}
/**
* Convert given object into string.
*
* @param value
* A value to check.
*
* @return Text representation of the value.
*/
String getStringValue(Object value)
{
if (value == null)
{
return null;
}
value = convertValue(value);
if (definition.getType() == AttributeType.ATTR_BOOLEAN)
{
return value.toString().toUpperCase();
}
if (definition.getType() == AttributeType.ATTR_BYTEARRAY)
{
return Base64.byteArrayToBase64((byte[]) value);
}
if (definition.getType() == AttributeType.ATTR_BITSELECTOR ||
definition.getType() == AttributeType.ATTR_BITFIELD)
{
return "'" + value.toString() + "'B";
}
return value.toString();
}
/**
* Get all values in one array.
*
* @param a
* The array into which the elements are to be stored, if it is
* big enough; otherwise, a new array of the same run-time type
* is allocated for this purpose.
*
* @return All values in one array or <code>null</code>.
*/
Object[] toArray(Object[] a)
{
if (values.size() == 0)
return null;
//Make sure that types match
if (!a.getClass().getComponentType().isInstance(getVariable()))
return null;
return values.toArray(a);
}
/**
* Check that provided object match the attribute definition.
*
* @param value
* An object to check
*
* @return <code>true</code> if parameter has valid type.
*/
private boolean checkType(Object value)
{
if (value == null)
{
return false;
}
return value.getClass().isInstance(getVariable());
}
/**
* This method returns a default value for the specified NodeAttribute
* definition.
*
* @return An instance of object of valid type for definition provided
* during attribute creation.
*/
private Object getVariable()
{
switch (definition.getType())
{
case AttributeType.ATTR_INTEGER:
return 0;
case AttributeType.ATTR_BOOLEAN:
return false;
case AttributeType.ATTR_STRING:
return new String("");
case AttributeType.ATTR_DOUBLE:
return 0.0d;
case AttributeType.ATTR_BYTEARRAY:
return new byte[0];
case AttributeType.ATTR_BITFIELD:
return new BitField(1);
case AttributeType.ATTR_BITSELECTOR:
return new BitSelector(1, 0);
case AttributeType.ATTR_DATE:
return new DateValue();
case AttributeType.ATTR_TIME:
return new TimeValue(0, 0);
}
return null;
}
/**
* Convert given <code>String</code> into value using attribute type
* definition to find conversion routine.
*
* @param str
* Source string
*
* @return Object of correct type or <code>null</code> is something is
* wrong with the source string.
*/
private Object stringToValue(String str)
{
if (str == null)
{
return null;
}
try
{
switch (definition.getType())
{
case AttributeType.ATTR_STRING:
return str;
case AttributeType.ATTR_BYTEARRAY:
return Base64.base64ToByteArray(str);
case AttributeType.ATTR_INTEGER:
return Integer.valueOf(str, 10);
case AttributeType.ATTR_BOOLEAN:
return Boolean.valueOf(str);
case AttributeType.ATTR_DOUBLE:
return Double.valueOf(str);
case AttributeType.ATTR_BITFIELD:
return BitField.valueOf(str);
case AttributeType.ATTR_BITSELECTOR:
return BitSelector.valueOf(str);
case AttributeType.ATTR_DATE:
return DateValue.valueOf(str);
case AttributeType.ATTR_TIME:
return TimeValue.valueOf(str);
}
}
catch (Exception e)
{
LOG.log(Level.FINE, "", e);
}
return null;
}
/**
* Converts a value of supported data type into a value of a native data type.
* <p>
* This method currently supports conversion of {@link BitSet} data type into
* {@link BitField}.
*
* @param value
* The value to convert.
*
* @return The converted result.
*/
private Object convertValue(Object value)
{
if (value == null)
{
return null;
}
if (value instanceof BitSet)
{
return BitSetHelper.toBitField((BitSet) value);
}
if (value instanceof Date)
{
return new DateValue((Date) value);
}
else
{
return value;
}
}
/**
* Replacement for the default object writing method.
*
* @param out
* The output destination to which fields will be saved.
*
* @throws IOException
* In case of I/O errors.
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException
{
writeExternalizable(out, definition);
writeList(out, ObjectOutput::writeObject, values);
}
/**
* Replacement for the default object reading method.
*
* @param in
* Input source from which fields will be restored.
*
* @throws IOException
* In case of I/O errors.
* @throws ClassNotFoundException
* If payload can't be instantiated.
*/
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
definition = readExternalizable(in, NodeAttribute::new);
values = readList(in, Vector::new, ObjectInput::readObject);
}
}