
com.tangosol.dev.component.Trait Maven / Gradle / Ivy
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.dev.component;
import com.tangosol.coherence.config.Config;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlValue;
import com.tangosol.util.Base;
import com.tangosol.util.ErrorList;
import com.tangosol.util.Listeners;
import com.tangosol.util.StringTable;
import com.tangosol.util.UID;
import com.tangosol.util.NullImplementation;
import com.tangosol.util.SimpleEnumerator;
import com.tangosol.util.StringMap;
import com.tangosol.util.IllegalStringException;
import java.beans.PropertyVetoException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.VetoableChangeListener;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.List;
import java.util.Iterator;
/**
* A trait is an abstract class representing a collection of attributes and
* a recursive container of traits.
*
* It's easy to imagine a component ... something with properties, methods,
* events, etc. It's easy to imagine an attribute ... one specific piece of
* information, like "this property is of type int" or "this behavior is
* final". What is difficult is imagining the assembly of attributes into
* a component such that specific questions can be asked of any piece of the
* component including the component itself. To simplify this, everything
* above the level of an attribute is represented as a Trait.
*
* A component, for example, is a trait which contains property traits,
* behavior traits, child component traits, implements/dispatches/integrates
* interface traits, and has attributes such as name, static, final, etc.
*
* - a Component is a trait which contains Component traits, Behavior traits,
* Property traits, and Interface traits
* - a Property is a trait of a Component
* - a Behavior is a trait of a Component which contains a ReturnValue trait
* and zero or more Parameter traits
* - a ReturnValue is a trait of a Behavior
* - a Parameter is a trait of a Behavior
* - an exception (Throwee) is a trait of a Behavior
*
* Each attribute of the trait is required to be a vetoable bean property,
* which means that:
*
* - for a given trait, any object can request to be a listener such that
* a vetoable PropertyChangeEvent is delivered before the change; in
* response to the PropertyChangeEvent, the listener can veto the change
* by throwing a PropertyVetoException
* - for a given trait, any object can request to be a listener such that
* a non-vetoable PropertyChangeEvent is delivered after the change is
* made
*
* Additionally, in order to simplify tool development, an listener interface
* exists that reports the addition, removal, and modification of sub-traits.
* Again, there is a vetoable (before) and non-vetoable (after) option for
* receiving these notifications.
*
* The trait itself incorporates several attributes:
*
* 1. A description, providing documentation for each component trait.
* Since component traits are derived and modified, the description can
* be derived and modified. This takes the form of providing additional
* documentation, replacing the documentation that already exists, or
* not changing the documentation at all.
*
* 2. A tip, which is a "short description" intended for user interface
* assistance, such as a "tool tip". This is considered to be part of
* the documentation.
*
* 3. A secondary unique identifier, sometimes referred to as a "doggy
* tag", which provides a way of repairing links between derived or
* modified traits when the primary identifier, such as a name, changes
* at the base level.
*
* 4. An origin, which describes whether the trait was added at this level,
* a base level (for modification), or a super level (for derivation).
* Additionally, if the trait was added at this level, the origin gives
* information as to the reason why it was added at this level. These
* reasons include:
*
* 1. The trait was added "manually"
* 2. The trait resulted automatically from another trait, for example:
* 1. implements
* 2. dispatches
* 3. integrates
* 4. property
*
* @version 0.50, 11/16/97
* @version 1.00, 08/07/98
* @author Cameron Purdy
*/
public abstract class Trait
extends Base
implements Constants
{
// ----- construction ---------------------------------------------------
/**
* Construct a Trait. This is the "default" constructor which is used by
* deriving classes. Additionally, this constructor is delegated to by
* all constructors of this class.
*
* @param parent the containing trait or null if this trait is not
* contained by another trait
* @param nMode one of RESOLVED, DERIVATION, MODIFICATION
*/
protected Trait(Trait parent, int nMode)
{
// assertion: (remove after testing) mode is valid
if (nMode != RESOLVED && nMode != DERIVATION && nMode != MODIFICATION)
{
throw new IllegalArgumentException(CLASS +
": Invalid mode for Trait construction (" + nMode + ")");
}
m_parent = parent;
m_nMode = nMode;
}
/**
* Construct a blank Trait. All derivable traits blank trait constructor.
* The result is one of:
*
* - A blank resolved trait
*
- A blank derivation
*
- A blank modification
*
* This is the constructor used by implementations of the
* getBlankDerivedTrait() method.
*
* @param base the super or base Trait to derive from
* @param parent the containing trait or null if this trait is not
* contained by another trait
* @param nMode one of RESOLVED, DERIVATION, MODIFICATION
*
* @see #getBlankDerivedTrait(Trait, int)
*/
protected Trait(Trait base, Trait parent, int nMode)
{
this(parent, nMode);
// assertion: (remove after testing) base is present and valid
if (base == null || base.getClass() != this.getClass())
{
throw new IllegalArgumentException(CLASS +
": Invalid base for blank Trait construction (" + base + ")");
}
m_uid = base.m_uid;
}
/**
* Copy constructor. This constructor is used by the the copy constructor
* of the deriving trait. All traits implement a copy constructor.
*
* Note: Event listeners are not copied.
*
* @param parent the trait that will contain the new copy of this trait
* @param that the trait to copy from
*/
protected Trait(Trait parent, Trait that)
{
this(parent, that.m_nMode);
StringTable tbl = that.m_tblOrigin;
if (tbl != null)
{
tbl = (StringTable) tbl.clone();
}
this.m_uid = that.m_uid;
this.m_nOrigin = that.m_nOrigin;
this.m_tblOrigin = tbl;
this.m_sTip = that.m_sTip;
this.m_sPrevTip = that.m_sPrevTip;
this.m_sDesc = that.m_sDesc;
this.m_sPrevDesc = that.m_sPrevDesc;
this.m_fReplaceDesc = that.m_fReplaceDesc;
this.m_fPrevReplaceDesc = that.m_fPrevReplaceDesc;
this.m_nState = that.m_nState;
}
/**
* Construct a Trait object from persistent data. All traits implement
* implement a constructor from persistent data.
*
* @param parent the trait that contains this trait
* @param stream the object to read the persistent information from
* @param nVersion version of the data structure in the stream
*
* @throws IOException if an exception occurs reading the trait data
*/
protected Trait(Trait parent, DataInput stream, int nVersion)
throws IOException
{
this(parent, /* mode= */ stream.readUnsignedByte());
m_sTip = readString(stream);
m_sDesc = readString(stream);
m_fReplaceDesc = stream.readBoolean();
// TODO 1999.11.15 cp potentially remove; this is a fix for
// blank descriptions that were saved before the description
// delta was fixed
if (m_fReplaceDesc && m_sDesc == BLANK)
{
m_fReplaceDesc = false;
}
// there may or may not be a UID in the stream
if (stream.readBoolean())
{
m_uid = new UID(stream);
}
// origin
m_nOrigin = stream.readUnsignedByte();
// origin traits
int cTraits = stream.readUnsignedByte();
if (cTraits > 0)
{
StringTable tbl = new StringTable();
for (int i = 0; i < cTraits; ++i)
{
tbl.add(stream.readUTF());
}
m_tblOrigin = tbl;
}
// set the trait's processing state to resolving
m_nState = STATE_RESOLVING;
}
/**
* Construct a Trait object from persistent data. All traits implement
* implement a constructor from persistent data.
*
* @param parent the trait that contains this trait
* @param xml the XmlElement to read the persistent information from
* @param nVersion version of the data structure in the stream
*
* @throws IOException if an exception occurs reading the trait data
*/
protected Trait(Trait parent, XmlElement xml, int nVersion)
throws IOException
{
this(parent, /* mode= */ parseMode(xml.getSafeElement("mode").getString()));
XmlElement xmlDesc = xml.getElement("description");
if (xmlDesc != null)
{
m_sTip = readString(xmlDesc.getElement("tip"));
m_sDesc = readString(xmlDesc.getElement("text"));
m_fReplaceDesc = readBoolean(xmlDesc.getElement("replace"));
}
// there may or may not be a UID in the stream
XmlElement xmlUid = xml.getElement("uid");
if (xmlUid != null)
{
m_uid = new UID(xmlUid.getString());
}
// origin
int nOrigin = ORIGIN_SUPER;
StringTable tblTraits = null;
XmlElement xmlOrigin = xml.getElement("origin");
if (xmlOrigin != null)
{
String sLevel = readString(xmlOrigin.getElement("level"));
if (sLevel.length() > 0)
{
switch (sLevel.charAt(0))
{
case 'T': case 't':
nOrigin = ORIGIN_THIS;
break;
case 'B': case 'b':
nOrigin = ORIGIN_BASE;
break;
case 'S': case 's':
nOrigin = ORIGIN_SUPER;
break;
default:
throw new IOException("invalid origin level: " + sLevel);
}
}
if (readBoolean(xmlOrigin.getElement("manual")))
{
nOrigin |= ORIGIN_MANUAL;
}
XmlElement xmlTraits = xmlOrigin.getElement("traits");
if (xmlTraits != null)
{
List listTraits = xmlTraits.getElementList();
for (Iterator iter = listTraits.iterator(); iter.hasNext(); )
{
XmlElement xmlTrait = (XmlElement) iter.next();
if (xmlTrait.getName().equals("trait"))
{
String sTrait = readString(xmlTrait);
if (sTrait.length() > 0)
{
if (tblTraits == null)
{
tblTraits = new StringTable();
nOrigin |= ORIGIN_TRAIT;
}
tblTraits.add(sTrait);
}
}
}
}
}
m_nOrigin = nOrigin;
m_tblOrigin = tblTraits;
// set the trait's processing state to resolving
m_nState = STATE_RESOLVING;
}
// ----- persistence ----------------------------------------------------
/**
* Save the Trait information to a stream. If derived traits have any
* data of their own, then they must implement (i.e. supplement) this
* method.
*
* This is a custom serialization implementation that is unrelated to the
* Serializable interface and the Java implementation of persistence.
*
* @param stream the object to write the persistent information to
*
* @throws IOException if an exception occurs reading the trait data
*/
protected synchronized void save(DataOutput stream)
throws IOException
{
if (!(m_nMode == RESOLVED || m_nMode == DERIVATION || m_nMode == MODIFICATION))
{
throw new IOException(CLASS + ".save: Trait contains invalid mode (" + toString() + ", " + m_nMode + ")");
}
stream.writeByte(m_nMode);
stream.writeUTF(m_sTip);
stream.writeUTF(m_sDesc);
stream.writeBoolean(m_fReplaceDesc);
// store the UID (if any)
boolean fHasUID = (m_uid != null);
stream.writeBoolean(fHasUID);
if (fHasUID)
{
m_uid.save(stream);
}
// origin
stream.writeByte(m_nOrigin);
// origin traits
StringTable tbl = m_tblOrigin;
int cTraits = (tbl == null ? 0 : tbl.getSize());
stream.writeByte(cTraits);
if (cTraits > 0)
{
for (Enumeration enmr = tbl.keys(); enmr.hasMoreElements(); )
{
stream.writeUTF((String) enmr.nextElement());
}
}
}
/**
* Save the Trait information to XML. If derived traits have any
* data of their own, then they must implement (i.e. supplement) this
* method.
*
* This is a custom serialization implementation that is unrelated to the
* Serializable interface and the Java implementation of persistence.
*
* @param xml an XmlElement to write the persistent information to
*
* @throws IOException if an exception occurs saving the trait data
*/
protected synchronized void save(XmlElement xml)
throws IOException
{
// store mode
String sMode;
switch (m_nMode)
{
case RESOLVED:
sMode = "resolved";
break;
case DERIVATION:
sMode = "derivation";
break;
case MODIFICATION:
sMode = "modification";
break;
default:
throw new IOException(CLASS + ".save: Trait contains invalid mode ("
+ toString() + ", " + m_nMode + ")");
}
xml.addElement("mode").setString(sMode);
// store description info
String sTip = m_sTip;
String sText = m_sDesc;
boolean fReplace = m_fReplaceDesc;
if (sTip != BLANK || sText != BLANK || fReplace)
{
XmlElement xmlDesc = xml.addElement("description");
if (sTip != BLANK)
{
xmlDesc.addElement("tip").setString(m_sTip);
}
if (sText != BLANK || fReplace)
{
xmlDesc.addElement("text").setString(sText);
}
if (fReplace)
{
xmlDesc.addElement("replace").setBoolean(true);
}
}
// store the UID (if any)
if (m_uid != null)
{
xml.addElement("uid").setString(m_uid.toString());
}
// origin
int nOrigin = m_nOrigin;
StringTable tblOrigin = m_tblOrigin;
if (nOrigin != ORIGIN_SUPER || (tblOrigin != null && !tblOrigin.isEmpty()))
{
XmlElement xmlOrigin = xml.addElement("origin");
String sLevel;
switch (nOrigin & ORIGIN_LEVEL)
{
case ORIGIN_THIS:
sLevel = "this";
break;
case ORIGIN_BASE:
sLevel = "base";
break;
case ORIGIN_SUPER:
sLevel = "super";
break;
default:
sLevel = "invalid";
break;
}
xmlOrigin.addElement("level").setString(sLevel);
if ((nOrigin & ORIGIN_MANUAL) != 0)
{
xmlOrigin.addElement("manual").setBoolean(true);
}
if (tblOrigin != null && !tblOrigin.isEmpty())
{
XmlElement xmlTraits = xmlOrigin.addElement("traits");
for (Enumeration enmr = tblOrigin.keys(); enmr.hasMoreElements(); )
{
xmlTraits.addElement("trait").setString((String) enmr.nextElement());
}
}
}
}
/**
* Helper for re-constituting strings. Guarantees that all 0-length
* strings are BLANK.
*
* @param stream the object to read the persistent information from
*
* @return the string that is read from the stream or BLANK if a 0-length
* string was read
*/
protected static String readString(DataInput stream)
throws IOException
{
String s = stream.readUTF();
return (s.length() > 0 ? s : BLANK);
}
/**
* Determine the component mode from a mode string, as would be encoded
* in an XML serialized format.
*
* @param s the mode string
*
* @return the component mode enumeration value
*/
private static int parseMode(String s)
{
int nMode = RESOLVED;
if (s != null && s.length() > 1)
{
switch (s.charAt(0))
{
case 'D': case 'd':
nMode = DERIVATION;
break;
case 'M': case 'm':
nMode = MODIFICATION;
break;
}
}
return nMode;
}
/**
* Helper for re-constituting strings. Guarantees that all 0-length
* strings are BLANK.
*
* @param xml the XmlValue to get a String value from
*
* @return the string from the XmlValue or BLANK if the string is 0-length
*/
protected static String readString(XmlValue xml)
{
String s = null;
if (xml != null)
{
s = xml.getString();
}
if (s == null || s.length() == 0)
{
s = BLANK;
}
return s;
}
/**
* Helper for reading booleans from XML. Assumes "false" for missing
* data.
*
* @param xml the XmlValue to get a boolean value from
*
* @return the boolean from the XmlValue or false if the value is missing
*/
protected static boolean readBoolean(XmlValue xml)
{
boolean f = false;
if (xml != null)
{
f = xml.getBoolean();
}
return f;
}
/**
* Helper for reading flags from XML.
*
* @param xml the XmlElement for the trait to get the flags from
* @param sName the name of the sub-element that stores the flags
* @param nDefault the default flags value
*
* @return the flags value
*/
protected static int readFlags(XmlElement xml, String sName, int nDefault)
{
int nFlags = nDefault;
xml = xml.getElement(sName);
if (xml != null)
{
String sFlags = xml.getString();
if (sFlags != null && sFlags.length() > 0)
{
if (sFlags.length() > 2 && sFlags.charAt(0) == '0'
&& ( sFlags.charAt(1) == 'x'
|| sFlags.charAt(1) == 'X'))
{
nFlags = Integer.parseInt(sFlags.substring(2), 16);
}
else
{
nFlags = Integer.parseInt(sFlags);
}
}
}
return nFlags;
}
/**
* Helper for writing flags to XML.
*
* @param xml the XmlElement for the trait to store the flags into
* @param sName the name of the sub-element that stores the flags
* @param nFlags the flags value
* @param nDefault the default flags value
*/
protected static void saveFlags(XmlElement xml, String sName, int nFlags, int nDefault)
{
if (nFlags != nDefault)
{
xml.addElement(sName).setString("0x" + toHexString(nFlags, 8));
}
}
/**
* Save a StringTable of traits to XML form.
*
* @param xml the XML element to save the sub-traits into
* @param tbl the StringTable of named sub-traits
* @param sTable the XML element name to use to enclose the table of
* sub-traits
* @param sEntry the XML element name to use for each sub-trait
*/
protected static void saveTable(XmlElement xml, StringTable tbl, String sTable, String sEntry)
throws IOException
{
if (tbl != null && !tbl.isEmpty())
{
xml = xml.addElement(sTable);
for (Enumeration enmr = tbl.elements(); enmr.hasMoreElements(); )
{
Trait trait = (Trait) enmr.nextElement();
trait.save(xml.addElement(sEntry));
}
}
}
/**
* Save a StringTable of keys to XML form.
*
* @param xml the XML element to save the String keys into
* @param tbl the StringTable of keys
* @param sTable the XML element name to use to enclose the keys
* @param sEntry the XML element name to use for each key
*
* @throws IOException if a problem occursing saving the keys to XML
*/
protected static void saveTableKeys(XmlElement xml, StringTable tbl, String sTable, String sEntry)
throws IOException
{
if (tbl != null && !tbl.isEmpty())
{
xml = xml.addElement(sTable);
for (Enumeration enmr = tbl.keys(); enmr.hasMoreElements(); )
{
xml.addElement(sEntry).setString((String) enmr.nextElement());
}
}
}
/**
* Read a StringTable of keys from XML.
*
* @param xml the XML element that contains the table of keys
* @param sTable the XML element name that encloses the keys
* @param sEntry the XML element name for each key
*
* @return the StringTable of keys, or null if there are no keys
*
* @throws IOException if a problem occursing reading the keys from XML
*/
protected static StringTable readTableKeys(XmlElement xml, String sTable, String sEntry)
throws IOException
{
StringTable tbl = null;
xml = xml.getElement(sTable);
if (xml != null)
{
List listEntry = xml.getElementList();
for (Iterator iter = listEntry.iterator(); iter.hasNext(); )
{
XmlElement xmlEntry = (XmlElement) iter.next();
if (xmlEntry.getName().equals(sEntry))
{
if (tbl == null)
{
tbl = new StringTable();
}
tbl.add(xmlEntry.getString());
}
}
}
return tbl;
}
/**
* Write a StringMap of data to XML.
*
* @param xml the XML to write the StringMap to
* @param sMap the name for the set of key/key mappings in the XML
* @param sEntry the name for each key/key mapping in the XML
* @param map the StringMap to write
*
* @throws IOException if there were any problems writing the StringMap
*/
protected static void saveStringMap(XmlElement xml, String sMap, String sEntry, StringMap map)
throws IOException
{
if (!map.isEmpty())
{
xml = xml.addElement(sMap);
Enumeration enmr = map.primaryStrings();
while (enmr.hasMoreElements())
{
String sKey = (String) enmr.nextElement();
String sValue = map.get(sKey);
XmlElement xmlEntry = xml.addElement(sEntry);
xmlEntry.addElement("map-from").setString(sKey);
xmlEntry.addElement("map-to").setString(sValue);
}
}
}
/**
* Read a StringMap of data from XML.
*
* @param xml the XML containing a StringMap
* @param sMap the name of the set of key/key mappings in the XML;
* the element does not have to exist in the XML
* @param sEntry the name of each key/key mapping in the XML; there
* do not have to be any key/key mappings
*
* @return a new StringMap, with the key/key mappings from the XML, if
* there were any
*
* @throws IOException if there were any problems reading the StringMap
*/
protected static StringMap readStringMap(XmlElement xml, String sMap, String sEntry)
throws IOException
{
StringMap map = new StringMap();
xml = xml.getElement(sMap);
if (xml != null)
{
List listEntry = xml.getElementList();
for (Iterator iter = listEntry.iterator(); iter.hasNext(); )
{
XmlElement xmlEntry = (XmlElement) iter.next();
if (xmlEntry.getName().equals(sEntry))
{
String sKey = readString(xmlEntry.getElement("map-from"));
String sValue = readString(xmlEntry.getElement("map-to"));
try
{
map.put(sKey, sValue);
}
catch (IllegalStringException e)
{
throw new IOException(e.getMessage());
}
}
}
}
return map;
}
// ----- derivation/modification ----------------------------------------
/**
* Construct a blank Trait. All traits implement this method.
*
* A blank derived trait is an "l-value" for resolve and extract
* processing. In other words, when resolve and extract create a result
* trait (either a resolved or delta trait) it is this method which is
* responsible for creating that trait in its initial state ("blank").
*
* The resolve and extract methods implemented by this class (Trait) are
* responsible for instantiating a resulting derived trait, either a
* RESOLVED, DERIVATION, or MODIFICATION trait, depending on which method
* (resolve or extract) was called and the parameters that were passed.
*
* This virtual method (getBlankDerivedTrait) is a "virtual constructor".
* In other words, it is responsible for instantiating the correct class,
* which is the same class as the "this" parameter (the base trait).
*
* @param parent the containing trait or null if the blank trait is
* not going to be contained by another trait
* @param nMode one of RESOLVED, DERIVATION, MODIFICATION
*
* @return a new blank derived trait of this trait's class with the
* specified parent and mode
*
* @see #resolve
* @see #extract
*/
protected abstract Trait getBlankDerivedTrait(Trait parent, int nMode);
/**
* Construct a null derivation or modification Trait. This method is
* overridden by traits that have a different null derivation/modification
* from their blank derived trait. The "this" parameter is the base
* trait.
*
* A null trait derivation or modification is an "r-value" for resolve
* processing; a null derivation or modification is used when the delta
* to apply is not present.
*
* @param parent the containing trait or null if the null trait is
* not going to be contained by another trait
* @param nMode one of DERIVATION or MODIFICATION
*
* @return a new null derivation or modification trait of this trait's
* class with the specified parent and mode
*/
protected Trait getNullDerivedTrait(Trait parent, int nMode)
{
if (nMode == RESOLVED)
{
throw new IllegalArgumentException(CLASS + ".getNullDerivedTrait: "
+ ": Invalid mode for null Trait construction (" + nMode + ")");
}
return getBlankDerivedTrait(parent, nMode);
}
/**
* Resolve the delta trait relative to the base trait before resolving
* the derived trait itself. This is necessary for those situations where
* the mode of the delta trait would "override" the mode of the base trait.
*
* Almost always this method will end up returning the original delta
* trait passed in. This method returns the extracted difference between
* the base trait and the delta trait when the base trait has been modified
* in such as way to cause the delta trait to override it.
*
* This method is called prior to calling the resolve method itself and must
* be performed seperate so the sub-trait can access the potentially changed
* delta trait.
*
* For clarity purposes, the resolveDelta implementation does not refer to
* the "this" parameter except to re-label it as the "base" Trait.
*
* @param delta the Trait derivation or modification
* @param loader the Loader object for JCS and CD dependencies
* @param errlist the error list object to log error information to
*
* @return the delta trait to be used during resolve
*
* @exception ComponentException thrown only if a fatal error occurs
*/
protected Trait resolveDelta(Trait delta, Loader loader, ErrorList errlist)
throws ComponentException
{
// there are two known Trait instances:
// base - the base Trait to apply the delta Trait to (this)
// delta - the MODIFICATION or DERIVATION Trait to apply (passed)
Trait base = this;
// assertion: base mode is legal
switch (base.m_nMode)
{
case RESOLVED:
case DERIVATION:
case MODIFICATION:
break;
default:
throw new IllegalStateException(CLASS + ".resolveDelta: "
+ "Illegal base mode (" + base.m_nMode + ") for resolving modification!");
}
// validate mode of the base and delta traits
// Dr = Br + Dd
// Dr = Br + Dm
// Dd = Bd + Dm
// Dm = Bm + Dm
switch (delta.m_nMode)
{
case DERIVATION:
if (base.m_nMode == RESOLVED)
{
break;
}
// fall through
case RESOLVED:
// it is not possible to apply a derived Trait to anything
// but a resolved Trait, furthermore, it is not possible to
// apply a resolved trait to anything (resolved, derived, or
// modified):
// Dr = Br + Dr resolved + resolved
// Dd = Bd + Dr derived + resolved
// Dm = Bm + Dr modified + resolved
// Dd = Bd + Dd derived + derived
// Dm = Bm + Dd modified + derived
// this can occur if the base is added after the derived
// already exists; to solve this, keep whatever is keepable
// by extracting the legal delta between the base and the
// would-be delta, for example:
// Dr = Br + (Dr - Br)
// the result may be a null derivation (no information could
// be salvaged) but at least it is a legal operation
// log an error for now, if this becomes intrusive then remove it
//logError(RESOLVE_FORCEEXTRACT, WARNING, new Object[]
// {delta.toString(), delta.toPathString()}, errlist);
delta = delta.extract(base, delta.m_parent, loader, errlist);
break;
case MODIFICATION:
// modification traits can be applied to resolved,
// derivation, or modification traits
break;
default:
throw new IllegalArgumentException(CLASS + ".resolveDelta: "
+ "Illegal delta mode (" + delta.m_nMode + ")");
}
return delta;
}
/**
* Create a derived/modified Trait by combining the information from this
* Trait with the super or base level's Trait information. Neither the
* base ("this") nor the delta may be modified in any way.
*
* All Trait classes implement this method. Their implementation must
* first call resolveDelta to resolve any discrepencies between the delta
* and the base Trait. Then, the Trait class implementation must call
* this implementation passing the delta Trait returned from resolveDelta
* in order to create the resulting derived Trait.
*
* For clarity purposes, the resolve implementation does not refer to the
* "this" parameter except to re-label it as the "base" Trait.
*
* @param delta the Trait derivation or modification
* @param parent the Trait which will contain the resulting Trait or
* null if the resulting Trait will not be contained
* @param loader the Loader object for JCS and CD dependencies
* @param errlist the error list object to log error information to
*
* @return the result of applying the specified delta Trait to this Trait
*
* @exception ComponentException thrown only if a fatal error occurs
*/
protected Trait resolve(Trait delta, Trait parent, Loader loader, ErrorList errlist)
throws ComponentException
{
// there are four known Trait instances:
// base - the base Trait to apply the delta Trait to (this)
// delta - the MODIFICATION or DERIVATION Trait to apply (passed from resolveDelta)
// parent - the parent of the resulting derived Trait (passed)
// derived - the resulting Trait
Trait base = this;
Trait derived = null;
// create the derived trait with the specified parent; note that
// resolve always results in a Trait with the same mode as the base
derived = base.getBlankDerivedTrait(parent, base.m_nMode);
// copy the tip from the delta; use the previous tip from the
// delta if available, otherwise use the tip from the base as
// the previous tip
derived.m_sTip = delta.m_sTip;
derived.m_sPrevTip = (delta.m_sPrevTip != BLANK ? delta.m_sPrevTip
: base.m_sTip != BLANK ? base.m_sTip
: base.m_sPrevTip);
// copy the description from the delta; if the delta overrode its
// base's description, then use its base's description as the
// previous ("undo") description, otherwise combine the delta's
// previous description with the base's description
derived.m_sDesc = delta.m_sDesc;
derived.m_fReplaceDesc = delta.m_fReplaceDesc;
if (delta.m_fPrevReplaceDesc)
{
derived.m_sPrevDesc = delta.m_sPrevDesc;
derived.m_fPrevReplaceDesc = true;
}
else
{
// build the description using what is already present in the
// delta Trait (due to the application of one or more trait
// modifications) and the description at the base level
derived.m_sPrevDesc = getDescription(delta.m_sPrevDesc,
base.getDescription(), false);
// determine if the base was replacing its base description
derived.m_fPrevReplaceDesc = base.m_fReplaceDesc || base.m_fPrevReplaceDesc;
}
// verify that UIDs match
if (base.m_uid == null)
{
derived.m_uid = delta.m_uid;
}
else if (!base.m_uid.equals(delta.m_uid))
{
logError(RESOLVE_UIDCHANGE, WARNING, new Object[]
{delta.toString(), delta.toPathString()}, errlist);
}
// determine level of origin
// 1) if base origin is super or if delta is a derivation then
// origin is super
// 2) otherwise origin is base
derived.m_nOrigin = (base.isFromSuper() || delta.m_nMode == DERIVATION ? ORIGIN_SUPER
: ORIGIN_BASE);
// copy manual origin from the delta
if (delta.isFromManual())
{
derived.setFromManual();
}
// mode of derived is always same as mode of base
derived.m_nMode = base.m_nMode;
// set the delta process state
delta.m_nState = STATE_RESOLVING;
return derived;
}
/**
* Finalize the resolve process. This means that this trait will not
* be asked to resolve again. A trait is considered designable after
* it has finalized the resolve process.
*
* @param loader the Loader object for JCS and CD dependencies
* @param errlist the error list object to log error information to
*
* @exception ComponentException thrown only if a fatal error occurs
*/
protected void finalizeResolve(Loader loader, ErrorList errlist)
throws ComponentException
{
// default tip to the base's (the "previous") tip
if (m_sTip == BLANK)
{
m_sTip = m_sPrevTip;
}
// make sure the trait is resolved
if (m_nMode != RESOLVED)
{
logError(RESOLVE_FORCERESOLVE, WARNING, new Object[]
{toString(), toPathString()}, errlist);
m_nMode = RESOLVED;
}
// m_fPrevReplaceDesc is used only during resolve processing
m_fPrevReplaceDesc = false;
// flag us has having been finalize resolved
m_nState = STATE_RESOLVED;
}
/**
* Determine the mode of extraction assuming the passed base Trait were
* being exctracted from this derived Trait.
*
* This method must be overridden by any Trait which can determine
* whether the extraction is for a DERIVATION or MODIFICATION.
* (Component is the only trait which can differentiate.)
*
* @param base the base Trait to extract
*
* @return DERIVATION or MODIFICATION
*/
protected int getExtractMode(Trait base)
{
if (this.m_nMode == RESOLVED && base.m_nMode == RESOLVED)
{
// could be either derivation or modification; ask the parent
return this.m_parent.getExtractMode(base.m_parent);
}
return MODIFICATION;
}
/**
* Create a derivation/modification Trait by determining the differences
* between the derived Trait ("this") and the passed base Trait. Neither
* the derived Trait ("this") nor the base may be modified in any way.
*
* All Trait classes implement this method. Their implementation must
* first call this implementation in order to create the resulting Trait.
*
* For clarity purposes, the extract implementation does not refer to the
* "this" parameter except to re-label it as the "derived" Trait.
*
* @param base the base Trait to extract
* @param parent the Trait which will contain the resulting Trait or
* null if the resulting Trait will not be contained
* @param loader the Loader object for JCS and CD dependencies
* @param errlist the error list object to log error information to
*
* @return the delta between this derived Trait and the base Trait
*
* @exception ComponentException thrown only if a fatal error occurs
*/
protected Trait extract(Trait base, Trait parent, Loader loader, ErrorList errlist)
throws ComponentException
{
// assertion - validate mode
int nMode = getExtractMode(base);
if (nMode != DERIVATION && nMode != MODIFICATION)
{
throw new IllegalArgumentException(CLASS + ".extract: "
+ "Illegal extract mode (" + nMode + ")");
}
// there are four known Trait instances:
// derived - the Trait which extends the base Trait (this)
// base - the base Trait to extract from the derived Trait (passed)
// parent - the parent of the resulting delta Trait (passed)
// delta - the resulting MODIFICATION or DERIVATION Trait
Trait derived = this;
Trait delta = base.getBlankDerivedTrait(parent, nMode);
// extract the tip and previous tip values
String sTip = derived.m_sTip;
String sPrevTip = derived.m_sPrevTip;
// check if this is the first extract performed
// against this trait
if (derived.m_nState == STATE_RESOLVED)
{
// check if the tip value is different than the
// previous tip value determined while resolving
// this trait
if (sTip.equals(sPrevTip))
{
sTip = BLANK;
}
// recalculate the previous tip value
// while extracting
sPrevTip = BLANK;
}
// if there is a tip value to worry about
if (sTip != BLANK)
{
// if this base specified a tip, use that as the previous
// tip value
if (base.m_sTip != BLANK)
{
sPrevTip = base.m_sTip;
}
else if (base.m_sPrevTip != BLANK)
{
sPrevTip = base.m_sPrevTip;
}
}
delta.m_sTip = sTip;
delta.m_sPrevTip = sPrevTip;
boolean fReplaceDesc = derived.m_fReplaceDesc;
String sDesc = derived.m_sDesc;
boolean fPrevReplaceDesc = derived.m_fPrevReplaceDesc;
String sPrevDesc = derived.m_sPrevDesc;
switch (derived.m_nState)
{
case STATE_RESOLVING:
// we are extracting the changes between
// two traits during a resolveDelta conflict
if (fReplaceDesc == base.m_fReplaceDesc
&& sDesc.equals(base.m_sDesc))
{
// we have two identical set of changes
fReplaceDesc = false;
sDesc = BLANK;
}
fPrevReplaceDesc = false;
sPrevDesc = BLANK;
break;
case STATE_RESOLVED:
// discard the "previous" description and
// recalculate on the way down
fPrevReplaceDesc = false;
sPrevDesc = BLANK;
// fall into default
default:
// TODO: Remove when all is determined to be okay...
// always carry the derived replace flag except for when creating
// a modification and the base and derived are both replace
//if (nMode == MODIFICATION
// && (base.m_fReplaceDesc || base.m_fPrevReplaceDesc)
// && fReplaceDesc)
// {
// fReplaceDesc = false;
// sDesc = derived.getDescription();
// }
// if there is a description to worry about
if (fReplaceDesc || sDesc != BLANK)
{
// keep track of the last previous value
if (base.m_fReplaceDesc || base.m_sDesc != BLANK)
{
fPrevReplaceDesc = base.m_fReplaceDesc;
sPrevDesc = base.m_sDesc;
}
else if (base.m_fPrevReplaceDesc || base.m_sPrevDesc != BLANK)
{
fPrevReplaceDesc = base.m_fPrevReplaceDesc;
sPrevDesc = base.m_sPrevDesc;
}
}
}
delta.m_fReplaceDesc = fReplaceDesc;
delta.m_sDesc = sDesc;
delta.m_fPrevReplaceDesc = fPrevReplaceDesc;
delta.m_sPrevDesc = sPrevDesc;
// verify that UIDs match
if (base.m_uid == null)
{
delta.m_uid = derived.m_uid;
}
else if (!base.m_uid.equals(derived.m_uid))
{
logError(EXTRACT_UIDCHANGE, WARNING, new Object[]
{derived.toString(), derived.toPathString()}, errlist);
}
// set the delta process state
delta.m_nState = STATE_EXTRACTING;
return delta;
}
/**
* Finalize the extract process. This means that this trait will not
* be asked to extract again. A trait is considered persistable after
* it has finalized the extract process.
*
* @param loader the Loader object for JCS and CD dependencies
* @param errlist the error list object to log error information to
*
* @exception ComponentException thrown only if a fatal error occurs
*/
protected synchronized void finalizeExtract(Loader loader, ErrorList errlist)
throws ComponentException
{
if (m_nMode != RESOLVED)
{
// don't store tip if it is redundant with the base level
// calculated while extracting
if (m_sTip.equals(m_sPrevTip))
{
m_sTip = BLANK;
}
// don't store the description if it is redundant with
// the base level calculated while extracting
if (m_fReplaceDesc == m_fPrevReplaceDesc
&& m_sDesc.equals(m_sPrevDesc))
{
m_fReplaceDesc = false;
m_sDesc = BLANK;
}
}
// don't store the base level tip
m_sPrevTip = BLANK;
// don't store the base description
m_fPrevReplaceDesc = false;
m_sPrevDesc = BLANK;
// discard all origin information (except for manual)
m_nOrigin &= ORIGIN_MANUAL;
m_tblOrigin = null;
}
/**
* Determine if the trait can be discarded. This has two uses:
*
* - Determining if a resolved trait should not exist; for example
* a trait without an origin
*
- An extracted trait that contains no information (i.e. a "null
* derivation") is often discardable
*
* For a resolved trait, this request is only legal after the
* finalizeResolve call. Likewise, for a derivation/modification
* trait, this request is only legal after the finalizeExtract call.
*
* @return true if the trait is discardable
*/
protected boolean isDiscardable()
{
switch (m_nMode)
{
case RESOLVED:
// resolved Traits are discardable if they have no origin;
// however, it is possible that a resolved trait is a child
// of a derivation/modification (i.e. it is a non-extractable
// child, which means that extracting its parent trait does
// not in turn extract this trait) in which case it is not
// discardable because it represents delta information
if (isFromNothing())
{
for (Trait trait = m_parent; trait != null; trait = trait.m_parent)
{
int nMode = trait.m_nMode;
if (nMode == DERIVATION || nMode == MODIFICATION)
{
return false;
}
}
}
else
{
return false;
}
break;
case DERIVATION:
case MODIFICATION:
// check for "delta" information at this level
if (m_fReplaceDesc || m_sTip != BLANK || m_sDesc != BLANK || isFromManual())
{
return false;
}
// check if contained traits are discardable
for (Enumeration enmr = getSubTraits(); enmr.hasMoreElements(); )
{
Trait trait = (Trait) enmr.nextElement();
if (!trait.isDiscardable())
{
return false;
}
}
break;
}
return true;
}
/**
* Determine whether this trait contains information that would cause
* generation of a class that would operate differently than the class
* generated from the information maintained by the super trait.
*
* @param base the base (i.e. the super) Trait to compare to
*
* @return true if a delta between this trait and its super trait means
* that class generation should generate the class corresponding
* to this trait, otherwise false
*/
protected boolean isClassDiscardable(Trait base)
{
// the Trait contains no information that would modify the generation
// of classes, but it does contain information that would modify the
// generation of source listings or even related classes (such as
// BeanInfo classes); it may seem odd that a delta in a description
// could force class generation, but it is hard to foresee what
// potential problems could exist if the opposite decision were made
return this.getDescription().equals(base.getDescription())
&& this.getTip ().equals(base.getTip ());
}
/**
* Helper for isClassDiscardable.
*/
protected boolean isClassDiscardableFromSubtraitTable(StringTable tblThis, StringTable tblThat)
{
if (!tblThis.keysEquals(tblThat))
{
return false;
}
for (Enumeration enmr = tblThis.keys(); enmr.hasMoreElements(); )
{
String s = (String) enmr.nextElement();
Trait traitThis = (Trait) tblThis.get(s);
Trait traitThat = (Trait) tblThat.get(s);
if (!traitThis.isClassDiscardable(traitThat))
{
return false;
}
}
return true;
}
/**
* Helper method to log derivation error information.
*
* @param sCode the error code
* @param nSeverity the error severity
* @param aoParam an array of replaceable parameters
* @param errlist the error list object to log error information to
*
* @exception DerivationException thrown when the error list fills up
*/
public void logError(String sCode, int nSeverity, Object[] aoParam, ErrorList errlist)
throws DerivationException
{
if (errlist != null)
{
try
{
errlist.add(new ErrorList.Item(sCode, nSeverity, null, aoParam, null, RESOURCES));
}
catch (ErrorList.OverflowException e)
{
throw new DerivationException("Error list full");
}
}
}
// ----- accessors ------------------------------------------------------
/**
* Determine if the Trait is modifiable in any way. Only resolved traits
* are modifiable. If the trait containing this trait is not modifiable,
* then neither is this trait.
*
* There is an underlying assumption that the trait is RESOLVED. Only
* RESOLVED traits are manipulated by the "tools". All other states
* are either intermediate or used for storage.
*
* @return true if the Trait is modifiable, false otherwise
*/
public boolean isModifiable()
{
// not modifiable if parent is not modifiable
return m_parent == null || m_parent.isModifiable();
}
// ----- Containment
/**
* Determine the trait which contains this trait.
*
* @return the containing trait or null if this trait is not contained
*/
protected Trait getParentTrait()
{
return m_parent;
}
/**
* Determine all traits which are contained by this trait. Any trait that
* contains other traits must implement this method.
*
* @return the an enumeration of traits contained by this trait
*/
protected Enumeration getSubTraits()
{
return NullImplementation.getEnumeration();
}
/**
* Determine the closest Component which contains this trait.
*
* @return the containing Component or null if this trait is not contained
* by a Component
*/
public Component getParentComponent()
{
Trait parent = getParentTrait();
while (parent != null && !(parent instanceof Component))
{
parent = parent.getParentTrait();
}
return (Component) parent;
}
// ----- Attribute: Mode
/**
* Determine whether the trait is a resolved trait, a derivation, or a
* modification.
*
* Note: It is also possible that the trait has been invalidated.
*
* @return one of RESOLVED, DERIVATION, MODIFICATION, or INVALID
*
* @see com.tangosol.dev.component.Constants#RESOLVED
* @see com.tangosol.dev.component.Constants#DERIVATION
* @see com.tangosol.dev.component.Constants#MODIFICATION
* @see com.tangosol.dev.component.Constants#INVALID
*/
public int getMode()
{
return m_nMode;
}
/**
* Set the mode of this trait.
*
* @param nMode a valid trait mode
*/
protected void setMode(int nMode)
{
if (nMode != m_nMode)
{
switch (nMode)
{
case INVALID:
case RESOLVED:
case DERIVATION:
case MODIFICATION:
m_nMode = nMode;
break;
default:
throw new IllegalArgumentException(CLASS + ".setMode: "
+ "Illegal mode value (" + nMode + ")");
}
}
}
// ----- Attribute: Process State
/**
*/
protected int getProcessState()
{
return m_nState;
}
/**
* Inform the trait that it is now valid.
*/
protected void validate()
{
// only validate if parent is already valid
if ((m_parent == null || m_parent.m_fDispatch) && !m_fDispatch)
{
m_fDispatch = true;
// validate contained traits
for (Enumeration enmr = getSubTraits(); enmr.hasMoreElements(); )
{
((Trait) enmr.nextElement()).validate();
}
}
}
/**
* Discard this trait and make sure it is marked as un-usable. Deriving
* traits implement this method. Deriving traits should invoke this
* implementation first (via super) before invalidating their own data.
*/
protected void invalidate()
{
if (m_nMode != INVALID)
{
m_fDispatch = false;
// discard listeners
notifyPre.removeAll();
notifyPost.removeAll();
notifySubPre.removeAll();
notifySubPost.removeAll();
// mark as invalidated
setMode(INVALID);
// invalidate contained traits
for (Enumeration enmr = getSubTraits(); enmr.hasMoreElements(); )
{
((Trait) enmr.nextElement()).invalidate();
}
// discard reference data, insuring that this trait will not
// affect previously related traits and other objects
// (this also theoretically facilitates garbage collection)
notifyPre = null;
notifyPost = null;
notifySubPre = null;
notifySubPost = null;
m_parent = null;
m_uid = null;
m_tblOrigin = null;
m_sTip = null;
m_sPrevTip = null;
m_sDesc = null;
m_sPrevDesc = null;
}
}
// ----- Attribute: Origin
/**
* Determine whether the trait was added at this level or at a previous
* level. This is equivalent to "isNotFromSuperOrBase", whether or not
* additional origins (manual, traits) exist at this level.
*
* @return true if the trait was added at this level
*/
public boolean isDeclaredAtThisLevel()
{
return (m_nOrigin & ORIGIN_LEVEL) == ORIGIN_THIS;
}
/**
* Determine if the trait existed at a base level. This means that the
* trait is the result of modification (but not the result of derivation).
*
* @return true if the trait existed at a base level (but did not exist
* at a super level)
*/
public boolean isFromBase()
{
return (m_nOrigin & ORIGIN_LEVEL) == ORIGIN_BASE;
}
/**
* Specify that the trait existed at a base level. This method was added
* to provide a means to fix traits that were deferred during resolve of
* modificaitions, since deferral until the derivation processing causes
* the origin to incorrectly be "this".
*/
protected void setFromBase()
{
// assertion: don't allow the trait to already be from super
azzert((m_nOrigin & ORIGIN_LEVEL) != ORIGIN_SUPER);
m_nOrigin = (m_nOrigin & ~ORIGIN_LEVEL) | ORIGIN_BASE;
}
/**
* Determine if the trait existed at a super level. This means that
* the trait is the result of derivation.
*
* @return true if the trait existed at a super level
*/
public boolean isFromSuper()
{
return (m_nOrigin & ORIGIN_LEVEL) == ORIGIN_SUPER;
}
/**
* Determine whether other traits contribute to this trait's origin at
* this level.
*
* @return true if this trait has other traits as part of its origin at
* this level
*/
public boolean isFromTrait()
{
return (m_nOrigin & ORIGIN_TRAIT) != 0;
}
/**
* Determine whether the specified trait contributes to this trait's
* origin at this level.
*
* @return true if the specified trait is part of this trait's origin at
* this level
*/
public boolean isFromTrait(Trait trait)
{
StringTable tbl = m_tblOrigin;
return tbl != null && tbl.contains(trait.getUniqueDescription());
}
/**
* Enumerate all traits that contribute to the origin of this trait at
* this level. (This means that only those traits declared at this
* level will contribute to this trait's origin.)
*
* (These cannot be the traits themselves since that would make
* persistence close to impossible, so instead the "unique description"
* is used.)
*
* @return an enumeration of trait descriptions that contribute to this
* trait's origin
*/
protected Enumeration getOriginTraits()
{
StringTable tbl = m_tblOrigin;
return (tbl == null ? NullImplementation.getEnumeration()
: tbl.keys());
}
/**
* Determine whether any traits with the specified trait descriptor
* contributes to this trait's origin at this level.
*
* @return true if at least one origin trait has the specified descriptor
*/
protected boolean isFromTraitDescriptor(String sDescriptor)
{
StringTable tbl = m_tblOrigin;
return tbl != null && tbl.stringsStartingWith(sDescriptor + ' ').length > 0;
}
/**
* Enumerate the names (not including descriptors) of all traits that
* contribute to the origin of this trait at this level.
*
* @return an enumeration of trait names for origin traits that begin with
* the specified descriptor string
*/
protected Enumeration getOriginTraits(String sDescriptor)
{
StringTable tbl = m_tblOrigin;
if (tbl == null)
{
return NullImplementation.getEnumeration();
}
String[] asDesc = tbl.stringsStartingWith(sDescriptor + ' ');
int cDesc = asDesc.length;
if (cDesc < 1)
{
return NullImplementation.getEnumeration();
}
for (int i = 0; i < cDesc; ++i)
{
String sDesc = asDesc[i];
asDesc[i] = sDesc.substring(sDesc.indexOf(' ') + 1);
}
return new SimpleEnumerator(asDesc);
}
/**
* Add the specified trait to the list of traits contributing to this
* trait's origin.
*
* @param trait the trait contributing to this trait's origin
*/
protected void addOriginTrait(Trait trait)
{
StringTable tbl = m_tblOrigin;
if (tbl == null)
{
m_tblOrigin = tbl = new StringTable();
m_nOrigin |= ORIGIN_TRAIT;
}
tbl.add(trait.getUniqueDescription());
}
/**
* Remove the specified trait from the list of traits contributing to
* this trait's origin.
*
* @param trait the trait contributing to this trait's origin
*/
protected void removeOriginTrait(Trait trait)
{
StringTable tbl = m_tblOrigin;
if (tbl != null)
{
tbl.remove(trait.getUniqueDescription());
if (tbl.isEmpty())
{
m_tblOrigin = null;
m_nOrigin &= ~ORIGIN_TRAIT;
}
}
}
/**
* Determines if the trait exists at this level regardless of whether
* it exists from derivation, implements, dispatches, or integrates.
*
* @return true if there is a manual origin
*/
public boolean isFromManual()
{
return (m_nOrigin & ORIGIN_MANUAL) != 0;
}
/**
* Set the manual origin.
*/
protected void setFromManual()
{
m_nOrigin |= ORIGIN_MANUAL;
}
/**
* Clear the manual origin.
*/
protected void clearFromManual()
{
m_nOrigin &= ~ORIGIN_MANUAL;
}
/**
* Determines if the trait exists for any non-manual reason.
*
* @return true if there is any non-manual origin
*/
public boolean isFromNonManual()
{
return (m_nOrigin & ~ORIGIN_MANUAL) != 0;
}
/**
* Determine if the trait has no origin.
*
* @return true if the trait has no origin
*/
public boolean isFromNothing()
{
return m_nOrigin == 0;
}
// ----- Attribute: UID
/**
* Access the UID for this trait. A UID provides an identification tag
* which is unrelated to the trait information itself, and allows
* derivation and modification traits to resolve major changes which
* occurred to their super/base traits. For example, if the data type of
* a parameter trait of a behavior trait of a component definition trait
* changes, the behavior's signature (calculated from the method name and
* parameter types) changes, but the UID wouldn't. The derived method
* would not have a matching signature super/base to resolve with, but
* using the UID, it could locate its super/base and resolve. (The
* alternative is to discard.)
*
* @return this trait's UID or null if no UID is assigned
*/
public UID getUID()
{
return m_uid;
}
/**
* Sets the UID for this trait.
*
* @param uid the UID for this trait, or null to remove the trait's UID
*/
protected void setUID(UID uid)
throws PropertyVetoException
{
setUID(uid, true);
}
/**
* Sets the UID for this trait.
*
* @param uid the UID for this trait, or null to remove the
* trait's UID
* @param fVetoable true if the setter can veto the value
*/
protected synchronized void setUID(UID uid, boolean fVetoable)
throws PropertyVetoException
{
UID prev = m_uid;
if (uid == null ? prev == null : uid.equals(prev))
{
return;
}
if (fVetoable)
{
if (!isModifiable())
{
readOnlyAttribute(ATTR_UID, prev, uid);
}
fireVetoableChange(ATTR_UID, prev, uid);
}
m_uid = uid;
firePropertyChange(ATTR_UID, prev, uid);
}
/**
* Make sure the trait has a UID.
*/
protected void assignUID()
{
if (m_uid == null)
{
try
{
setUID(new UID(), false);
}
catch (PropertyVetoException e)
{
throw new IllegalStateException(CLASS + ".assignUID: "
+ "Unexpected Veto Exception!");
}
}
}
/**
* Make sure the trait doesn't have a UID.
*/
protected void clearUID()
{
try
{
setUID(null, false);
}
catch (PropertyVetoException e)
{
throw new IllegalStateException(CLASS + ".assignUID: "
+ "Unexpected Veto Exception!");
}
}
/**
* Determine the unique name for this trait.
*
* A sub-trait that exists within a collection of sub-traits must have
* two ways of being identified:
*
* - A unique name, which is a string identifier
*
- A UID as a secondary identifier (dog tag)
*
* @return the primary string identifier of the trait
*/
protected abstract String getUniqueName();
/**
* Determine the unique description for this trait. The unique
* description is in the format:
*
* + ' ' +
*
* All traits must be uniquely identifiable by a "description string".
* This enables the origin implementation built into Trait to use the
* description string to differentiate between Trait origins.
*
* @return the description string for the trait
*/
protected abstract String getUniqueDescription();
/**
* Helper method to create a hash-table to look up sub-trait primary
* string ids by their secondary UIDs (dog tags).
*
* @param enmr an enumeration of sub-traits
*
* @return a hash-table whose key is UID and whose value is a unique
* string identifier of the trait
*/
protected static Hashtable getUIDTable(Enumeration enmr)
{
// create a hash-table to store the Trait UID's in
Hashtable tbl = new Hashtable();
while (enmr.hasMoreElements())
{
Trait trait = (Trait) enmr.nextElement();
tbl.put(trait.getUID(), trait.getUniqueName());
}
return tbl;
}
// ----- Attribute: Tip
/**
* Access the tip which is present at this level. A tip is a short
* description suitable for displaying as a tool tip, in a status line,
* etc.
*
* @return this trait's tip, which will be 0-length if no tip exists
*/
public String getTip()
{
return m_sTip;
}
/**
* Determine if the tip can be set.
*
* @return true if the tip is settable, false otherwise
*/
public boolean isTipSettable()
{
return isModifiable();
}
/**
* Stores the tip for this level.
*
* @param sTip the tip for this level, or blank to use the tip from a
* previous level
*/
public void setTip(String sTip)
throws PropertyVetoException
{
setTip(sTip, true);
}
/**
* Stores the tip for this level.
*
* @param sTip the tip for this level, or blank to use the tip from
* a previous level
* @param fVetoable true if the setter can veto the value
*/
protected synchronized void setTip(String sTip, boolean fVetoable)
throws PropertyVetoException
{
String sPrev = m_sTip;
if (sTip == null)
{
sTip = BLANK;
}
if (sTip.equals(sPrev))
{
return;
}
if (fVetoable)
{
if (!isTipSettable())
{
readOnlyAttribute(ATTR_TIP, sPrev, sTip);
}
if (sTip.length() == 0)
{
sTip = m_sPrevTip;
}
fireVetoableChange(ATTR_TIP, sPrev, sTip);
}
m_sTip = sTip;
firePropertyChange(ATTR_TIP, sPrev, sTip);
}
// ----- Attribute: Text
/**
* Access the description which is present at this level. To access the
* entire description, use the getDescription method.
*
* @return this level's description, which will be 0-length if no
* description exists at this level
*/
public String getText()
{
return m_sDesc;
}
/**
* Determine if the full-length textual description for this level can be
* set.
*
* @return true if the description is settable, false otherwise
*/
public boolean isTextSettable()
{
return isModifiable();
}
/**
* Stores the description for this level.
*
* @param sDesc this description for this level
*/
public void setText(String sDesc)
throws PropertyVetoException
{
setText(sDesc, true);
}
/**
* Stores the description for this level.
*
* @param sDesc this description for this level
* @param fVetoable true if the setter can veto the value
*/
protected synchronized void setText(String sDesc, boolean fVetoable)
throws PropertyVetoException
{
String sPrev = getText();
if (sDesc == null || sDesc.length() == 0)
{
sDesc = BLANK;
}
if (sDesc.equals(sPrev))
{
return;
}
if (fVetoable)
{
if (!isTextSettable())
{
readOnlyAttribute(ATTR_TEXT, sPrev, sDesc);
}
fireVetoableChange(ATTR_TEXT, sPrev, sDesc);
}
m_sDesc = sDesc;
firePropertyChange(ATTR_TEXT, sPrev, sDesc);
}
// ----- Attribute: ReplaceDescription
/**
* Determine if the description adds to any previous level's description
* or if it replaces the description from previous levels.
*
* @return true if this level's description replaces the description from
* previous levels, false if it adds to the description from
* previous levels
*/
public boolean isReplaceDescription()
{
return m_fReplaceDesc;
}
/**
* Determine if the DescriptionReplaced attribute can be changed.
*
* @return true if the DescriptionReplaced attribute can be changed
*/
public boolean isReplaceDescriptionSettable()
{
return isModifiable();
}
/**
* Toggle whether the description adds to any previous level's description
* or whether it replaces the description from previous levels.
*
* @param fReplaceDesc true if this level's description should replace
* the description from previous levels, false if it
* should add to the description from previous levels
*/
public void setReplaceDescription(boolean fReplaceDesc)
throws PropertyVetoException
{
setReplaceDescription(fReplaceDesc, true);
}
/**
* Toggle whether the description adds to any previous level's description
* or whether it replaces the description from previous levels.
*
* @param fReplaceDesc true if this level's description should replace
* the description from previous levels, false if it
* should add to the description from previous levels
* @param fVetoable true if the setter can veto the value
*/
protected synchronized void setReplaceDescription(boolean fReplaceDesc, boolean fVetoable)
throws PropertyVetoException
{
boolean fPrev = m_fReplaceDesc;
if (fReplaceDesc == fPrev)
{
return;
}
Boolean value = toBoolean(fReplaceDesc);
Boolean prev = toBoolean(fPrev);
if (fVetoable)
{
if (!isReplaceDescriptionSettable())
{
readOnlyAttribute(ATTR_REPDESC, prev, value);
}
fireVetoableChange(ATTR_REPDESC, prev, value);
}
m_fReplaceDesc = fReplaceDesc;
firePropertyChange(ATTR_REPDESC, prev, value);
}
// ----- Attribute: Description (calculated from Text and DescriptionReplaced)
/**
* Get the trait's description.
*
* @return this trait's description, which will be 0-length if no
* description exists
*/
public String getDescription()
{
return getDescription(m_sDesc, m_sPrevDesc, m_fReplaceDesc);
}
/**
* Build a trait description.
*
* @return the trait description
*/
protected static String getDescription(String sDesc, String sPrevDesc, boolean fReplaceDesc)
{
if (fReplaceDesc || sPrevDesc == BLANK)
{
// this description fully replaces any previous level's
return sDesc;
}
else if (sDesc == BLANK)
{
// there is no description at this level
return sPrevDesc;
}
else
{
// this description builds on any previous level's
return sPrevDesc + '\n' + sDesc;
}
}
/**
* Determine if the full-length textual description can be set.
*
* @return true if the description is settable, false otherwise
*/
public boolean isDescriptionSettable()
{
return isTextSettable();
}
/**
* Stores the trait's description. The description attribute is a
* combination of the Text and DescriptionReplaced attributes in
* that the Text attribute only access this level's description
* but the Description attribute returns the entire description and
* potentially sets both this level's text and whether this level's
* text overrides or adds to the super level's text.
*
* @param sDesc the trait's description
*/
public void setDescription(String sDesc)
throws PropertyVetoException
{
setDescription(sDesc, true);
}
/**
* Stores the trait's description. The description attribute is a
* combination of the Text and DescriptionReplaced attributes in
* that the Text attribute only access this level's description
* but the Description attribute returns the entire description and
* potentially sets both this level's text and whether this level's
* text overrides or adds to the super level's text.
*
* @param sDesc the trait's description
* @param fVetoable true if the setter can veto the value
*/
protected synchronized void setDescription(String sDesc, boolean fVetoable)
throws PropertyVetoException
{
String sPrevDesc = getDescription();
if (sDesc == null || sDesc.length() == 0)
{
sDesc = BLANK;
}
if (sDesc.equals(sPrevDesc))
{
return;
}
if (fVetoable)
{
if (!isDescriptionSettable())
{
readOnlyAttribute(ATTR_DESC, sPrevDesc, sDesc);
}
fireVetoableChange(ATTR_DESC, sPrevDesc, sDesc);
}
// determine if the description adds to or replaces the super
// level's description
m_fReplaceDesc = !sDesc.startsWith(m_sPrevDesc);
// if the new description doesn't replace the super level's
// description, then determine what is added
if (!m_fReplaceDesc)
{
sDesc = extractDescription(sDesc, m_sPrevDesc);
}
m_sDesc = sDesc;
firePropertyChange(ATTR_DESC, sPrevDesc, sDesc);
}
protected static String extractDescription(String sThisDesc, String sBaseDesc)
{
int cchThisDesc = sThisDesc.length();
int cchBaseDesc = sBaseDesc.length();
if (cchThisDesc == cchBaseDesc ||
cchThisDesc == cchBaseDesc + 1 && sThisDesc.charAt(cchBaseDesc) == '\n')
{
sThisDesc = BLANK;
}
else if (cchBaseDesc > 0 && cchThisDesc > cchBaseDesc && sThisDesc.startsWith(sBaseDesc))
{
// keep everything after the super description/linefeed
int cchLF = (sThisDesc.charAt(cchBaseDesc) == '\n' ? 1 : 0);
sThisDesc = sThisDesc.substring(cchBaseDesc + cchLF);
}
return sThisDesc;
}
// ----- Object methods -------------------------------------------------
/**
* Compare this Trait's UID to another Trait's UID.
*
* @param that the other Trait
*
* @return true if this Trait's UID matches that Trait's UID
*/
public boolean equalsUID(Trait that)
{
return (this.m_uid == null ? that.m_uid == null
: this.m_uid.equals(that.m_uid));
}
/**
* Compare this Trait's origin to another Trait's origin.
*
* @param that the other Trait
*
* @return true if this Trait's origin matches that Trait's origin
*/
public boolean equalsOrigin(Trait that)
{
if (this.m_nOrigin != that.m_nOrigin)
{
return false;
}
if (this.m_tblOrigin != null)
{
return this.m_tblOrigin.equals(that.m_tblOrigin);
}
return true;
}
/**
* Compare this Trait's origin to another Trait's origin, ignoring the
* manual origin information.
*
* @param that the other Trait
*
* @return true if this Trait's origin matches that Trait's origin
* (other than the manual origin)
*/
protected boolean equalsOriginSansManual(Trait that)
{
if (((this.m_nOrigin ^ that.m_nOrigin) & ~ORIGIN_MANUAL) != 0)
{
return false;
}
if (this.m_tblOrigin != null)
{
return this.m_tblOrigin.equals(that.m_tblOrigin);
}
return true;
}
/**
* Compare this Trait to another Object for equality. This method is
* implemented by each trait.
*
* @param obj the other Object to compare to this
*
* @return true if this Trait equals that Object
*/
public boolean equals(Object obj)
{
if (obj instanceof Trait)
{
Trait that = (Trait) obj;
return this == that
|| this.m_nMode == that.m_nMode
&& this.m_fReplaceDesc == that.m_fReplaceDesc
&& this.m_sTip .equals(that.m_sTip )
&& this.m_sDesc .equals(that.m_sDesc )
&& this.equalsOrigin(that)
&& this.equalsUID(that);
// PJM Removed the following because they are not persistent fields
//&& this.m_fPrevReplaceDesc == that.m_fPrevReplaceDesc
//&& this.m_sPrevDesc.equals(that.m_sPrevDesc)
//&& this.m_sPrevTip .equals(that.m_sPrevTip )
}
return false;
}
/**
* Provide a short human-readable description of the trait.
*
* @return a human-readable description of this trait
*/
public String toString()
{
return getUniqueDescription();
}
/**
* Provide a short human-readable hierarchical path to the trait.
*
* @return a human-readable description of this trait
*/
public String toPathString()
{
Trait parent = m_parent;
return (parent == null ? toString()
: parent.toPathString() + ", " + toString());
}
// ----- notifications --------------------------------------------------
/**
* Add a VetoableChangeListener to the listener list.
*
* @param listener The VetoableChangeListener to be added
*/
public void addVetoableChangeListener(VetoableChangeListener listener)
{
notifyPre.add(listener);
}
/**
* Remove a VetoableChangeListener from the listener list.
*
* @param listener The VetoableChangeListener to be removed
*/
public void removeVetoableChangeListener(VetoableChangeListener listener)
{
notifyPre.remove(listener);
}
/**
* Add a PropertyChangeListener to the listener list.
*
* @param listener The PropertyChangeListener to be added
*/
public void addPropertyChangeListener(PropertyChangeListener listener)
{
notifyPost.add(listener);
}
/**
* Remove a PropertyChangeListener from the listener list.
*
* @param listener The PropertyChangeListener to be removed
*/
public void removePropertyChangeListener(PropertyChangeListener listener)
{
notifyPost.remove(listener);
}
/**
* Add a VetoableSubChangeListener to the listener list.
*
* @param listener The VetoableSubChangeListener to be added
*/
public void addVetoableSubChangeListener(VetoableSubChangeListener listener)
{
notifySubPre.add(listener);
}
/**
* Remove a VetoableSubChangeListener from the listener list.
*
* @param listener The VetoableSubChangeListener to be removed
*/
public void removeVetoableSubChangeListener(VetoableSubChangeListener listener)
{
notifySubPre.remove(listener);
}
/**
* Add a SubChangeListener to the listener list.
*
* @param listener The SubChangeListener to be added
*/
public void addSubChangeListener(SubChangeListener listener)
{
notifySubPost.add(listener);
}
/**
* Remove a SubChangeListener from the listener list.
*
* @param listener The SubChangeListener to be removed
*/
public void removeSubChangeListener(SubChangeListener listener)
{
notifySubPost.remove(listener);
}
/**
* This helper function makes sure that the boolean true is always
* the Boolean TRUE and the boolean false is always the Boolean FALSE.
*
* This method is used by the setters for boolean properties in order
* to pass an object to a property change event corresponding to the
* previous and new boolean values of the proeprty.
*
* @param f either true or false
*
* @return either Boolean.TRUE or Boolean.FALSE
*/
protected static Boolean toBoolean(boolean f)
{
return f ? Boolean.TRUE : Boolean.FALSE;
}
/**
* Throw an exception when an attempt is made to set a read-only property.
*
* @param sAttribute the attribute name
* @param oPrevVal the value of the attribute
* @param oNewVal the value that the caller attempted to set the
* read-only attribute to
*
* @exception PropertyVetoException this exception is always thrown
*/
protected void readOnlyAttribute(String sAttribute, Object oPrevVal, Object oNewVal)
throws PropertyVetoException
{
String sDesc = RESOURCES.getString(ATTR_READONLY, new String[] {sAttribute},
"The \"{0}\" attribute is not modifiable.");
PropertyChangeEvent evt =
new PropertyChangeEvent(this, sAttribute, oPrevVal, oNewVal);
throw new PropertyVetoException(sDesc, evt);
}
/**
* Throw an exception when an attempt is made to set property to an
* illegal value.
*
* @param sAttribute the attribute name
* @param oPrevVal the value of the attribute
* @param oNewVal the value that the caller attempted to set the
* attribute to
*
* @exception PropertyVetoException this exception is always thrown
*/
protected void illegalAttributeValue(String sAttribute, Object oPrevVal, Object oNewVal)
throws PropertyVetoException
{
String sDesc = RESOURCES.getString(ATTR_ILLEGAL, new String[] {sAttribute},
"An attempt was made to set the \"{0}\" attribute to an illegal value.");
PropertyChangeEvent evt =
new PropertyChangeEvent(this, sAttribute, oPrevVal, oNewVal);
throw new PropertyVetoException(sDesc, evt);
}
/**
* Throw an exception when an invalid attempt is made to add a sub-trait.
*
* @param sAttribute the attribute name
*
* @exception PropertyVetoException this exception is always thrown
*/
protected void subNotAddable(String sAttribute, Object oValue)
throws PropertyVetoException
{
String sDesc = RESOURCES.getString(ATTR_NO_ADD, new String[] {sAttribute},
"The \"{0}\" sub-trait is not addable.");
PropertyChangeEvent evt =
new PropertyChangeEvent(this, sAttribute, null, oValue);
throw new PropertyVetoException(sDesc, evt);
}
/**
* Throw an exception when an invalid attempt is made to remove a
* sub-trait.
*
* @param sAttribute the attribute name
*
* @exception PropertyVetoException this exception is always thrown
*/
protected void subNotRemovable(String sAttribute, Object oValue)
throws PropertyVetoException
{
String sDesc = RESOURCES.getString(ATTR_NO_REMOVE, new String[] {sAttribute},
"The \"{0}\" sub-trait is not removable.");
PropertyChangeEvent evt =
new PropertyChangeEvent(this, sAttribute, oValue, null);
throw new PropertyVetoException(sDesc, evt);
}
/**
* Throw an exception when an invalid attempt is made to un-remove a
* sub-trait.
*
* @param sAttribute the attribute name
*
* @exception PropertyVetoException this exception is always thrown
*/
protected void subNotUnremovable(String sAttribute, Object oValue)
throws PropertyVetoException
{
String sDesc = RESOURCES.getString(ATTR_NO_UNREMOVE, new String[] {sAttribute},
"The \"{0}\" sub-trait is not unremovable.");
PropertyChangeEvent evt =
new PropertyChangeEvent(this, sAttribute, null, oValue);
throw new PropertyVetoException(sDesc, evt);
}
/**
* Fire the vetoable change event for an attribute.
*
* @param sAttribute name of attribute
* @param oPrevVal previous value of attribute
* @param oNewVal new value of attribute
*
* @exception PropertyVetoException if a notified object rejects the
* proposed attribute change
*/
protected void fireVetoableChange(String sAttribute, Object oPrevVal, Object oNewVal)
throws PropertyVetoException
{
fireAttributeChange(sAttribute, oPrevVal, oNewVal, CTX_VETO, null);
}
/**
* Fire the property change event (non-vetoable) for an attribute.
*
* @param sAttribute name of attribute
* @param oPrevVal previous value of attribute
* @param oNewVal new value of attribute
*/
protected void firePropertyChange(String sAttribute, Object oPrevVal, Object oNewVal)
{
try
{
fireAttributeChange(sAttribute, oPrevVal, oNewVal, CTX_DONE, null);
}
catch (PropertyVetoException e)
{
throw new RuntimeException(CLASS + ".firePropertyChange: "
+ "Illegal PropertyVetoException: " + e.toString());
}
}
/**
* Fire the vetoable change event for an attribute. Assumption is that
* most of the time there are zero listeners, occasionally there may be
* one listener, and very rarely there is more than one.
*
* @param sAttribute name of attribute
* @param oPrevVal previous value of attribute
* @param oNewVal new value of attribute
* @param nContext one of CTX_VETO, CTX_UNDO, CTX_DONE
* @param oStop for undo events only, the last listener to issue an
* undo event to
*
* @exception PropertyVetoException if a notified object rejects the
* proposed attribute change and the notification is vetoable
*/
private void fireAttributeChange(String sAttribute, Object oPrevVal, Object oNewVal, int nContext, Object oStop)
throws PropertyVetoException
{
// no events are dispatched until the trait is validated
if (!m_fDispatch)
{
return;
}
// pre (veto/undo) or post (done)
boolean fPre = (nContext != CTX_DONE);
// fire property change event
PropertyChangeEvent evtChange = null;
Listeners notify = (fPre ? notifyPre : notifyPost);
EventListener[] listeners = notify.listeners();
int cListeners = listeners.length;
for (int iListener = 0; iListener < cListeners; ++iListener)
{
if (evtChange == null)
{
// create event object
evtChange = new PropertyChangeEvent(this, sAttribute, oPrevVal, oNewVal);
}
switch (nContext)
{
case CTX_VETO:
{
VetoableChangeListener listener = (VetoableChangeListener) listeners[iListener];
try
{
listener.vetoableChange(evtChange);
}
catch (PropertyVetoException e)
{
// issue undo events
fireAttributeChange(sAttribute, oNewVal, oPrevVal, CTX_UNDO, listener);
// re-throw veto
throw e;
}
}
break;
case CTX_UNDO:
{
VetoableChangeListener listener = (VetoableChangeListener) listeners[iListener];
try
{
listener.vetoableChange(evtChange);
}
catch (PropertyVetoException e)
{
// undo cannot be vetoed
}
// check if this is the last listener to issue an undo to
if (listener == oStop)
{
return;
}
}
break;
case CTX_DONE:
((PropertyChangeListener) listeners[iListener]).propertyChange(evtChange);
break;
}
}
// fire sub change event up the trait containment chain
SubChangeEvent evtSub = null;
for (Trait trait = m_parent; trait != null; trait = trait.m_parent)
{
notify = (fPre ? trait.notifySubPre : trait.notifySubPost);
listeners = notify.listeners();
cListeners = listeners.length;
for (int iListener = 0; iListener < cListeners; ++iListener)
{
if (evtSub == null)
{
if (evtChange == null)
{
evtChange = new PropertyChangeEvent(this, sAttribute, oPrevVal, oNewVal);
}
evtSub = new SubChangeEvent(this, this, SUB_CHANGE, nContext, evtChange);
}
switch (nContext)
{
case CTX_VETO:
{
VetoableSubChangeListener listener = (VetoableSubChangeListener) listeners[iListener];
try
{
listener.vetoableSubChange(evtSub);
}
catch (PropertyVetoException e)
{
// issue undo events
fireAttributeChange(sAttribute, oNewVal, oPrevVal, CTX_UNDO, listener);
// re-throw veto
throw e;
}
}
break;
case CTX_UNDO:
{
VetoableSubChangeListener listener = (VetoableSubChangeListener) listeners[iListener];
try
{
listener.vetoableSubChange(evtSub);
}
catch (PropertyVetoException e)
{
// undo cannot be vetoed
}
// check if this is the last listener to issue an undo to
if (listener == oStop)
{
return;
}
}
break;
case CTX_DONE:
((SubChangeListener) listeners[iListener]).subChange(evtSub);
break;
}
}
}
}
/**
* Fire the sub trait change event which occurs when a trait is added,
* removed, or un-removed from this trait.
*
* @param traitSub the sub-trait being added, removed, or un-removed
* @param nAction the action occuring to the sub-trait
*
* @exception PropertyVetoException if a notified object rejects the
* proposed action
*/
protected void fireVetoableSubChange(Trait traitSub, int nAction)
throws PropertyVetoException
{
fireSubChange(traitSub, nAction, CTX_VETO, null);
}
/**
* Fire the traitSubChange event which occurs when a trait is added,
* removed, or un-removed from this trait.
*
* @param traitSub the sub-trait being added, removed, or un-removed
* @param nAction the action occuring to the sub-trait
*/
protected void fireSubChange(Trait traitSub, int nAction)
{
try
{
fireSubChange(traitSub, nAction, CTX_DONE, null);
}
catch (PropertyVetoException e)
{
throw new RuntimeException(CLASS + ".fireSubChange: "
+ "Illegal PropertyVetoException: " + e.toString());
}
}
/**
* Fire the vetoable sub change event for a trait. Assumption is that
* most of the time there are zero listeners, occasionally there may be
* one listener, and very rarely there is more than one.
*
* @param traitSub the sub-trait being added, removed, or un-removed
* @param nAction the action occuring to the sub-trait
* @param nContext one of CTX_VETO, CTX_UNDO, CTX_DONE
* @param oStop for undo events only, the last listener to issue an
* undo event to
*
* @exception PropertyVetoException if a notified object rejects the
* proposed attribute change and the notification is vetoable
*/
private void fireSubChange(Trait traitSub, int nAction, int nContext, Object oStop)
throws PropertyVetoException
{
// no events are dispatched until the trait is validated
if (!m_fDispatch)
{
return;
}
// fire sub change event up the trait containment chain
SubChangeEvent evt = null;
for (Trait trait = this; trait != null; trait = trait.m_parent)
{
Listeners notify = (nContext == CTX_DONE ? trait.notifySubPost
: trait.notifySubPre);
EventListener[] listeners = notify.listeners();
int cListeners = listeners.length;
for (int iListener = 0; iListener < cListeners; ++iListener)
{
if (evt == null)
{
evt = new SubChangeEvent(this, traitSub, nAction, nContext, null);
}
switch (nContext)
{
case CTX_VETO:
{
VetoableSubChangeListener listener = (VetoableSubChangeListener) listeners[iListener];
try
{
listener.vetoableSubChange(evt);
}
catch (PropertyVetoException e)
{
// issue undo events
int nUndoAction = (nAction == SUB_REMOVE ? SUB_ADD : SUB_REMOVE);
fireSubChange(traitSub, nUndoAction, CTX_UNDO, listener);
// re-throw veto
throw e;
}
}
break;
case CTX_UNDO:
{
VetoableSubChangeListener listener = (VetoableSubChangeListener) listeners[iListener];
try
{
listener.vetoableSubChange(evt);
}
catch (PropertyVetoException e)
{
// undo cannot be vetoed
}
// check if this is the last listener to issue an undo to
if (listener == oStop)
{
return;
}
}
break;
case CTX_DONE:
((SubChangeListener) listeners[iListener]).subChange(evt);
break;
}
}
}
}
// ----- debugging ------------------------------------------------------
/**
* Print the entire set of trait information to standard output.
*/
public void dump()
{
PrintWriter out = getOut();
dumpTree(out, BLANK);
out.println();
dump(out, BLANK);
}
/**
* Print the trait hierarchy to the specified PrintWriter object using
* the specified indentation.
*
* @param out the PrintWriter object to dump the information to
* @param sIndent a string used to indent each line of dumped information
*/
public void dumpTree(PrintWriter out, String sIndent)
{
out.println(sIndent + getUniqueDescription());
for (Enumeration enmr = getSubTraits(); enmr.hasMoreElements(); )
{
Trait trait = (Trait) enmr.nextElement();
trait.dumpTree(out, nextIndent(sIndent));
}
}
/**
* Print the entire set of trait information to the specified PrintWriter
* object using the specified indentation.
*
* @param out the PrintWriter object to dump the information to
* @param sIndent a string used to indent each line of dumped information
*/
public void dump(PrintWriter out, String sIndent)
{
final String NULL = "";
String sMode;
switch (m_nMode)
{
case RESOLVED:
sMode = "resolved";
break;
case DERIVATION:
sMode = "derivation";
break;
case MODIFICATION:
sMode = "modification";
break;
case INVALID:
sMode = "invalid";
break;
default:
sMode = "";
break;
}
String sState;
switch (m_nState)
{
case STATE_NEW:
sState = "new";
break;
case STATE_RESOLVING:
sState = "resolving";
break;
case STATE_RESOLVED:
sState = "resolved";
break;
case STATE_EXTRACTING:
sState = "extracting";
break;
default:
sState = "";
break;
}
out.print (sIndent + "Parent=");
out.println(m_parent == null ? NULL : m_parent.toString() + " (depth=" + getDepth() + ")");
out.print (sIndent + "Mode=" + sMode);
out.print (", Processing State=" + sState);
out.print (", Modifiable=" + toBoolean(isModifiable()).toString());
out.println(", UID=" + (m_uid == null ? NULL : m_uid.toString()));
String sLevel;
switch (m_nOrigin & ORIGIN_LEVEL)
{
case ORIGIN_THIS:
sLevel = "this";
break;
case ORIGIN_BASE:
sLevel = "base";
break;
case ORIGIN_SUPER:
sLevel = "super";
break;
default:
sLevel = "";
break;
}
out.print (sIndent + "Origin level=" + sLevel);
out.print (", Manual=" + toBoolean((m_nOrigin & ORIGIN_MANUAL) != 0).toString());
out.print (", Trait=" + toBoolean((m_nOrigin & ORIGIN_TRAIT ) != 0).toString());
out.println(" " + m_tblOrigin);
out.println(sIndent + "Tip=" + dump(m_sTip));
out.println(sIndent + "Previous Tip=" + dump(m_sPrevTip));
out.print (sIndent + "Description ");
out.print (m_fReplaceDesc ? "(replaced)=" : "(added)=");
out.println(indentString(dump(m_sDesc), sIndent, false));
out.print (sIndent + "Previous Description ");
out.print (m_fPrevReplaceDesc ? "(replaced)=" : "(added)=");
out.println(indentString(dump(m_sPrevDesc), sIndent, false));
}
protected String dump(String s)
{
if (s == BLANK)
{
return "";
}
if (s == null)
{
return "";
}
return '\"' + s + '\"';
}
/**
* Print the entire set of trait information to the specified PrintWriter
* object using the specified indentation.
*
* @param out the PrintWriter object to dump the information to
* @param sIndent a string used to indent each line of dumped information
* @param tbl the StringTable whose elements are traits
* @param sDesc a descrition string
*/
protected static void dump(PrintWriter out, String sIndent, StringTable tbl, String sDesc)
{
out.println(sIndent + (tbl == null ? "" : "" + tbl.getSize()) + ' ' + sDesc);
if (tbl == null)
{
return;
}
if (!tbl.isEmpty())
{
String[] as = tbl.strings();
int c = as.length;
for (int i = 0; i < c; ++i)
{
String sName = as[i];
Trait trait = (Trait) tbl.get(sName);
out.print(sIndent + "[" + i + "] " + sName + ":");
if (trait == null)
{
out.println(" ");
}
else
{
out.println();
trait.dump(out, nextIndent(sIndent));
}
}
}
}
/**
* Helper for dump to indent sub-trait dumps.
*
* @param sIndent the current indentation string
*
* @return the next level's indentation string
*/
protected static String nextIndent(String sIndent)
{
return sIndent + " ";
}
/**
* Determine the number of levels (parent/child) down this Trait is.
* For example, a global Component Definition is at level 0, the child
* of a global Component Definition is at level 1, and so forth.
*
* @return the number of levels down that this Trait is located
*/
protected int getDepth()
{
int cLevels = 0;
Trait trait = this;
while (trait.m_parent != null)
{
trait = trait.m_parent;
++cLevels;
}
return cLevels;
}
/**
* Helper for debugging methods to print an array of objects.
*
* @param ao an array of object to print
*
* @return a string containing the contents of the array
*/
protected static String arrayDescription(Object[] ao)
{
if (ao == null)
{
return "";
}
int c = ao.length;
if (c == 0)
{
return "";
}
StringBuffer sb = new StringBuffer();
sb.append('[')
.append(c)
.append("] (");
for (int i = 0; i < c; ++i)
{
if (i > 0)
{
sb.append(", ");
}
sb.append(ao[i].toString());
}
sb.append(')');
return sb.toString();
}
/**
* Helper for debugging methods to print a description of the flags.
*
* @param nFlags corresponds to the bit flags used by the Component
* Definition and various traits
* @param nMask specifies which flag attibutes to display; this is a
* bitwise combination of xxx_SPECIFIED values
* @param fSpecOnly pass true to only display the attributes from nMask
* which are specified in nFlags (mainly for
* modifications)
*
* @return a string of flag descriptions
*/
protected static String flagsDescription(int nFlags, int nMask, boolean fSpecOnly)
{
StringBuffer sb = new StringBuffer();
if ((nMask & MISC_ISTHROWABLE) != 0 && (nFlags & MISC_ISTHROWABLE) != 0)
{
sb.append(" throwable");
}
if ((nMask & MISC_ISINTERFACE) != 0)
{
sb.append((nFlags & MISC_ISINTERFACE) == 0 ? " class" : " interface");
}
if ( (nMask & EXISTS_SPECIFIED) != 0 &&
((nFlags & EXISTS_SPECIFIED) != 0 || !fSpecOnly))
{
switch (nFlags & EXISTS_MASK)
{
case EXISTS_NOT:
sb.append(" non-existent");
break;
case EXISTS_INSERT:
sb.append(" insert");
break;
case EXISTS_UPDATE:
sb.append(" update");
break;
case EXISTS_DELETE:
sb.append(" delete");
break;
default:
sb.append(" ");
break;
}
}
if ( (nMask & ACCESS_SPECIFIED) != 0 &&
((nFlags & ACCESS_SPECIFIED) != 0 || !fSpecOnly))
{
switch (nFlags & ACCESS_MASK)
{
case ACCESS_PUBLIC:
sb.append(" public");
break;
case ACCESS_PROTECTED:
sb.append(" protected");
break;
case ACCESS_PACKAGE:
sb.append(" package-private");
break;
case ACCESS_PRIVATE:
sb.append(" private");
break;
default:
sb.append(" ");
break;
}
}
if ( (nMask & SYNC_SPECIFIED) != 0 &&
((nFlags & SYNC_SPECIFIED) != 0 || !fSpecOnly))
{
sb.append((nFlags & SYNC_MASK) == SYNC_MONITOR ? " synchronized" : " no-monitor");
}
if ( (nMask & SCOPE_SPECIFIED) != 0 &&
((nFlags & SCOPE_SPECIFIED) != 0 || !fSpecOnly))
{
sb.append((nFlags & SCOPE_MASK) == SCOPE_INSTANCE ? " instance" : " static");
}
if ( (nMask & IMPL_SPECIFIED) != 0 &&
((nFlags & IMPL_SPECIFIED) != 0 || !fSpecOnly))
{
sb.append((nFlags & IMPL_MASK) == IMPL_CONCRETE ? " concrete" : " abstract");
}
if ( (nMask & DERIVE_SPECIFIED) != 0 &&
((nFlags & DERIVE_SPECIFIED) != 0 || !fSpecOnly))
{
sb.append((nFlags & DERIVE_MASK) == DERIVE_DERIVABLE ? " derivable" : " final");
}
if ( (nMask & ANTIQ_SPECIFIED) != 0 &&
((nFlags & ANTIQ_SPECIFIED) != 0 || !fSpecOnly))
{
sb.append((nFlags & ANTIQ_MASK) == ANTIQ_CURRENT ? " current" : " deprecated");
}
if ( (nMask & STG_SPECIFIED) != 0 &&
((nFlags & STG_SPECIFIED) != 0 || !fSpecOnly))
{
sb.append((nFlags & STG_MASK) == STG_PERSIST ? " persistent" : " transient");
}
if ( (nMask & DIST_SPECIFIED) != 0 &&
((nFlags & DIST_SPECIFIED) != 0 || !fSpecOnly))
{
sb.append((nFlags & DIST_MASK) == DIST_LOCAL ? " local" : " remote");
}
if ( (nMask & DIR_SPECIFIED) != 0 &&
((nFlags & DIR_SPECIFIED) != 0 || !fSpecOnly))
{
switch (nFlags & DIR_MASK)
{
case DIR_IN:
sb.append(" in");
break;
case DIR_OUT:
sb.append(" out");
break;
case DIR_INOUT:
sb.append(" inout");
break;
default:
sb.append(" ");
break;
}
}
if ( (nMask & VIS_SPECIFIED) != 0 &&
((nFlags & VIS_SPECIFIED) != 0 || !fSpecOnly))
{
switch (nFlags & VIS_MASK)
{
case VIS_SYSTEM:
sb.append(" system");
break;
case VIS_HIDDEN:
sb.append(" hidden");
break;
case VIS_ADVANCED:
sb.append(" advanced");
break;
case VIS_VISIBLE:
sb.append(" visible");
break;
default:
sb.append(" ");
break;
}
}
if ( (nMask & PROP_SPECIFIED) != 0 &&
((nFlags & PROP_SPECIFIED) != 0 || !fSpecOnly))
{
switch (nFlags & PROP_MASK)
{
case PROP_SINGLE:
sb.append(" single");
break;
case PROP_INDEXED:
sb.append(" indexed");
break;
case PROP_INDEXEDONLY:
sb.append(" indexed only");
break;
default:
sb.append(" ");
break;
}
}
return (sb.length() > 0 ? sb.toString().substring(1) : "");
}
// ----- public constants -----------------------------------------------
/**
* The UID attribute name.
*/
public static final String ATTR_UID = "UID";
/**
* The Tip attribute name.
*/
public static final String ATTR_TIP = "Tip";
/**
* The Text attribute name.
*/
public static final String ATTR_TEXT = "Text";
/**
* The ReplaceDescription attribute name.
*/
public static final String ATTR_REPDESC = "ReplaceDescription";
/**
* The Description attribute name.
*/
public static final String ATTR_DESC = "Description";
// ----- other constants ------------------------------------------------
/**
* The name of this class.
*/
private static final String CLASS = "Trait";
/**
* Debug flag. Allows for conditional debugging behavior.
*/
protected static final boolean DEBUG;
static
{
boolean fDebug = false;
String sDebug = Config.getProperty("coherence.component.debug");
if (sDebug != null && sDebug.length() > 0)
{
char ch = sDebug.charAt(0);
switch (ch)
{
case '1':
case 'Y':
case 'y':
case 'T':
case 't':
fDebug = true;
}
}
DEBUG = fDebug;
}
/**
* Bitmask: What level did this trait originate at?
*/
private static final int ORIGIN_LEVEL = 0x03;
/**
* Bitmask: This trait originated at this level.
*/
private static final int ORIGIN_THIS = 0x00;
/**
* Bitmask: This trait originated at a base (modification) level.
*/
private static final int ORIGIN_BASE = 0x01;
/**
* Bitmask: This trait originated at a super (derivation) level.
*/
private static final int ORIGIN_SUPER = 0x02;
/**
* Bitmask: At this level, the trait exists for a "manual" reason.
*/
private static final int ORIGIN_MANUAL = 0x04;
/**
* Bitmask: At this level, the trait's origin is implied by another
* trait (or other traits).
*/
private static final int ORIGIN_TRAIT = 0x08;
// ----- data members ---------------------------------------------------
/**
* A list of objects to notify with vetoable property changes.
*/
private Listeners notifyPre = new Listeners();
/**
* A list of objects to notify with property changes.
*/
private Listeners notifyPost = new Listeners();
/**
* A list of objects to notify with vetoable sub changes.
*/
private Listeners notifySubPre = new Listeners();
/**
* A list of objects to notify with sub changes.
*/
private Listeners notifySubPost = new Listeners();
/**
* The traits which contain this traits (or null if this trait is not
* contained).
*/
private Trait m_parent;
/**
* The mode of the trait: RESOLVED, DERIVATION, or MODIFICATION.
*/
private int m_nMode;
/**
* The trait's UID.
*/
private UID m_uid = null;
/**
* The trait's origin.
*/
private int m_nOrigin;
/**
* Other traits that are part of this trait's origin.
*/
private StringTable m_tblOrigin;
/**
* The tip.
*/
private String m_sTip = BLANK;
/**
* The tip from the previous extract level.
*/
private transient String m_sPrevTip = BLANK;
/**
* The description.
*/
private String m_sDesc = BLANK;
/**
* The description from previous levels.
*/
private String m_sPrevDesc = BLANK;
/**
* The mode of the description; false adds to the description, true replaces it.
*/
private boolean m_fReplaceDesc = false;
/**
* The mode of the previous description; this value is used only during resolve
* processing.
*/
private transient boolean m_fPrevReplaceDesc = false;
/**
* Until a trait is owned by another trait, it does not dispatch events.
*/
private transient boolean m_fDispatch = false;
/**
* The processing state of the trait: STATE_NEW, STATE_RESOLVING, STATE_RESOLVED,
* or STATE_EXTRACTING.
*/
private transient int m_nState = STATE_NEW;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy