com.tangosol.dev.component.Trait Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of coherence Show documentation
Show all versions of coherence Show documentation
Oracle Coherence Community Edition
/*
* 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;
}