nextapp.echo.app.Component Maven / Gradle / Ivy
/*
* This file is part of the Echo Web Application Framework (hereinafter "Echo").
* Copyright (C) 2002-2009 NextApp, Inc.
*
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*/
package nextapp.echo.app;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import nextapp.echo.app.event.EventListenerList;
/**
* A representation of an Echo component. This is an abstract base class from
* which all Echo components are derived.
*
* A hierarchy of Component
objects is used to represent the
* state of an application's user interface. A Component
may have
* a single parent Component
and may contain zero or more child
* Component
s. Certain Component
s may limit the
* number or type(s) of children which may be added to them, and may even
* establish requirements for what type(s) of parent Component
s
* they may be added to. In the event that an application attempts to add a
* child Component
to a parent Component
in spite
* of these requirements, an IllegalChildException
is thrown.
*
*
Properties and Styles
*
* The state of a single Component
is represented by its
* properties. Properties can be categorized into two types: "style" and
* "non-style". Style properties are generally used to represent the
* "look-and-feel" of a Component--information such as colors, fonts, location,
* and borders. "Non-style" properties are generally used to represent
* non-stylistic information such as data models, selection models, and locale.
*
* "Style Properties" have a special definition because they may be stored in
* Style
or StyleSheet
objects instead of as
* properties of a specific Component
instance. Property values
* contained in a relevant Style
or StyleSheet
* will be used for rendering when the property values are not specified by a
* Component
itself. Style properties are identified by the
* presence of a public static constant name in a Component
* implementation with the prefix PROPERTY_
. In the base
* Component
class itself there are several examples of style
* properties, such as PROPERTY_BACKGROUND
,PROPERTY_FONT
* and PROPERTY_LAYOUT_DATA
. The rendering application container
* will use the Component.getRenderProperty()
and
* Component.getRenderIndexedProperty()
to retrieve the values of
* stylistic properties, in order that their values might be obtained from
* the Component
's shared Style
or the
* ApplicationInstance
's StyleSheet
in the event
* they are not directly set in the Component
.
*
* A Component
implementation should not store the values of
* style properties as instance variables. Rather, the values of style
* properties should be stored in the local Style
instance, by
* way of the set()
method. The
* get()
method may be used to obtain the value of such
* properties. Only style properties should be stored using these methods;
* properties such as models should never be stored using the
* get()
/set()
interface.
*
*
Events
*
* Many Component
s will provide the capability to register
* EventListener
s to notify interested parties when various
* state changes occur. The base Component
class provides an
* EventListenerList
as a convenient and memory efficient means
* of storing such listeners. The internal EventListenerList
may
* be obtained using the getEventListenerList()
method. The
* EventListenerList
is lazy-created and will only be
* instantiated on the first invocation of the
* getEventListenerList()
method. If the intent is only to
* inquire about the state of event listeners without necessarily forcing
* instantiation of an EventListenerList
, the
* hasEventListenerList()
should be queried prior to invoking
* getEventListenerList()
.
*/
public abstract class Component
implements RenderIdSupport, Serializable {
/** Serial Version UID. */
private static final long serialVersionUID = 20070101L;
/**
* ArrayList
capacity for child storage.
*/
private static final int CHILD_LIST_CAPACITY = 3;
/**
* Empty array returned by getComponents()
when a
* Component
has no children.
*/
private static final Component[] EMPTY_COMPONENT_ARRAY = new Component[0];
/**
* Flag indicating the Component
is currently in the process of being disposed.
*/
private static final int FLAG_DISPOSE_IN_PROGRESS = 0x20;
/**
* Flag indicating the Component
is enabled.
*/
private static final int FLAG_ENABLED = 0x1;
/**
* Flag indicating the Component
is visible.
*/
private static final int FLAG_VISIBLE = 0x2;
/**
* Flag indicating that the Component
is currently undergoing
* initialization.
*/
private static final int FLAG_INIT_IN_PROGRESS = 0x10;
/**
* Flag indicating that the Component
is initialized.
*/
private static final int FLAG_INITIALIZED = 0x40;
/**
* Flag indicating that the Component
is currently undergoing
* registration to an ApplicationInstance
.
*/
private static final int FLAG_REGISTERING = 0x8;
/**
* Property change event name for immediate children being made visible/invisible.
* When used, the newValue
of the event will represent a child made visible,
* OR the oldValue
will represent a child made invisible.
*/
public static final String CHILD_VISIBLE_CHANGED_PROPERTY = "childVisible";
/**
* Property change event name for immediate children being added/removed.
* When used, the newValue
of the event will represent an added child,
* OR the oldValue
will represent a removed child.
*/
public static final String CHILDREN_CHANGED_PROPERTY = "children";
/** Property change event name for enabled state changes. */
public static final String ENABLED_CHANGED_PROPERTY = "enabled";
/** Property change event name for next focus traversal component changes. */
public static final String FOCUS_NEXT_ID_CHANGED_PROPERTY = "focusNextId";
/** Property change event name for previous focus traversal component changes. */
public static final String FOCUS_PREVIOUS_ID_CHANGED_PROPERTY = "focusPreviousId";
/** Property change event name for layout direction changes. */
public static final String LAYOUT_DIRECTION_CHANGED_PROPERTY = "layoutDirection";
/** Property change event name for locale changes. */
public static final String LOCALE_CHANGED_PROPERTY = "locale";
/** Property change event name for referenced Style
changes. */
public static final String STYLE_CHANGED_PROPERTY = "style";
/** Property change event name for named Style
changes. */
public static final String STYLE_NAME_CHANGED_PROPERTY = "styleName";
/** Property change event name for visibility changes. */
public static final String VISIBLE_CHANGED_PROPERTY = "visible";
public static final String PROPERTY_BACKGROUND = "background";
public static final String PROPERTY_FONT = "font";
public static final String PROPERTY_FOREGROUND = "foreground";
public static final String PROPERTY_LAYOUT_DATA = "layoutData";
/**
* Verifies a character can be used as initial character in a renderId
* Character must be a (7-bit ASCII) letter.
*
* @param ch the character to verify
* @return true if the character is a (7-bit ASCII) letter.
*/
private static final boolean isRenderIdStart(char ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
}
/**
* Verifies a character can be used as a non-initial character in a renderId.
* Character must be a (7-bit ASCII) letter, digit, or underscore.
*
* @param ch the character to verify
* @return true if the character is a (7-bit ASCII) letter, digit, or underscore.
*/
private static final boolean isRenderIdPart(char ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_';
}
/** The ApplicationInstance
to which the component is registered. */
private ApplicationInstance applicationInstance;
/**
* An ordered collection of references to child components.
* This object is lazily instantiated.
*/
private List children;
/**
* Boolean flags for this component, including enabled state and visibility.
* Multiple booleans are wrapped in a single integer for memory use reduction.
*/
private int flags;
/**
* A user-defined identifier for this component.
* This identifier is not related in any way to renderId
.
*/
private String id;
/**
* The layout direction of the component.
* This property is generally unset, as layout direction information is
* normally inherited from the ApplicationInstance
or from
* an ancestor Component
in the hierarchy.
*/
private LayoutDirection layoutDirection;
/** Listener storage. */
private EventListenerList listenerList;
/**
* The locale of the component.
* This property is generally unset, as locale information is normally
* inherited from the ApplicationInstance
or from an ancestor
* Component
in the hierarchy.
*/
private Locale locale;
/** Local style data storage for properties directly set on component itself. */
private MutableStyle localStyle;
/** The parent component. */
private Component parent;
/**
* The property change event dispatcher.
* This object is lazily instantiated.
*/
private PropertyChangeSupport propertyChangeSupport;
/**
* A application-wide unique identifier for this component.
* This identifier is not related in any way to id
.
*/
private String renderId;
/** Shared style. */
private Style sharedStyle;
/** Name of style to use from application style sheet */
private String styleName;
/** Render id of previous focus traversal component. */
private String focusPreviousId;
/** Render id of next focus traversal component. */
private String focusNextId;
/**
* Creates a new Component
.
*/
public Component() {
super();
flags = FLAG_ENABLED | FLAG_VISIBLE;
localStyle = new MutableStyle();
}
/**
* Adds the specified Component
as a child of this
* Component
. The child will be added at the greatest
* index.
*
* @param c the child Component
to add
*/
public void add(Component c) {
add(c, -1);
}
/**
* Adds the specified Component
as the n
th
* child of this component.
* All component-add operations use this method to add components.
* Component
s that require notification of all child additions
* should override this method (making sure to call the superclass'
* implementation).
* If the child component currently has a parent in another hierarchy, it
* will automatically be removed from that hierarchy before being added
* to this component. This behavior will also occur if the child component
* is currently a child of this component.
*
* @param c the child component to add
* @param n the index at which to add the child component, or -1 to add the
* component at the end
* @throws IllegalChildException if the child is not allowed to be added
* to this component, because it is either not valid for the
* component's state or is of an invalid type
*/
public void add(Component c, int n)
throws IllegalChildException {
// Ensure child is acceptable to this component.
if (!isValidChild(c)) {
throw new IllegalChildException(this, c);
}
// Ensure child component finds this component acceptable as a parent.
if (!c.isValidParent(this)) {
throw new IllegalChildException(this, c);
}
// Remove child from it's current parent if required.
if (c.parent != null) {
c.parent.remove(c);
}
// Lazy-create child collection if necessary.
if (children == null) {
children = new ArrayList(CHILD_LIST_CAPACITY);
}
// Connect child to parent.
c.parent = this;
if (n == -1) {
children.add(c);
} else {
children.add(n, c);
}
// Flag child as registered.
if (applicationInstance != null) {
c.register(applicationInstance);
}
// Notify PropertyChangeListeners of change.
firePropertyChange(CHILDREN_CHANGED_PROPERTY, null, c);
// Initialize component.
c.doInit();
}
/**
* Adds a property change listener to this Component
.
*
* @param l the listener to add
*/
public void addPropertyChangeListener(PropertyChangeListener l) {
if (propertyChangeSupport == null) {
propertyChangeSupport = new PropertyChangeSupport(this);
}
propertyChangeSupport.addPropertyChangeListener(l);
}
/**
* Adds a property change listener to this Component
for a specific property.
*
* @param propertyName the name of the property for which to listen
* @param l the listener to add
*/
public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
if (propertyChangeSupport == null) {
propertyChangeSupport = new PropertyChangeSupport(this);
}
propertyChangeSupport.addPropertyChangeListener(propertyName, l);
}
/**
* Internal method to set the render identifier of theComponent
.
* This method is invoked by the ApplicationInstance
* when the component is registered or unregistered, or by manual
* invocation of setRenderId()
. This method performs no
* error checking.
*
* @param renderId the new identifier
* @see #getRenderId()
* @see #setRenderId(java.lang.String)
*/
void assignRenderId(String renderId) {
this.renderId = renderId;
}
/**
* Life-cycle method invoked when the Component
is removed
* from a registered hierarchy. Implementations should always invoke
* super.dispose()
.
* Modifications to the component hierarchy are not allowed within this
* method.
*/
public void dispose() { }
/**
* Recursively executes the dispose()
life-cycle methods of
* this Component
and its descendants.
*/
void doDispose() {
if (applicationInstance == null) {
return;
}
if ((flags & (FLAG_INIT_IN_PROGRESS | FLAG_DISPOSE_IN_PROGRESS)) != 0) {
throw new IllegalStateException(
"Attempt to dispose component when initialize or dispose operation already in progress.");
}
flags |= FLAG_DISPOSE_IN_PROGRESS;
try {
if (children != null) {
Iterator it = children.iterator();
while (it.hasNext()) {
((Component) it.next()).doDispose();
}
}
if ((flags & FLAG_INITIALIZED) == 0) {
// Component already disposed.
return;
}
dispose();
flags &= ~FLAG_INITIALIZED;
} finally {
flags &= ~FLAG_DISPOSE_IN_PROGRESS;
}
}
/**
* Recursively executes the init()
life-cycle methods of
* this Component
and its descendants.
*/
void doInit() {
if (applicationInstance == null) {
return;
}
if ((flags & FLAG_INITIALIZED) != 0) {
// Component already initialized.
return;
}
if ((flags & (FLAG_INIT_IN_PROGRESS | FLAG_DISPOSE_IN_PROGRESS)) != 0) {
throw new IllegalStateException(
"Attempt to initialize component when initialize or dispose operation already in progress.");
}
flags |= FLAG_INIT_IN_PROGRESS;
try {
init();
flags |= FLAG_INITIALIZED;
if (children != null) {
Iterator it = children.iterator();
while (it.hasNext()) {
((Component) it.next()).doInit();
}
}
} finally {
flags &= ~FLAG_INIT_IN_PROGRESS;
}
}
/**
* Reports a bound property change to PropertyChangeListener
s
* and to the ApplicationInstance
's update management system.
*
* @param propertyName the name of the changed property
* @param oldValue the previous value of the property
* @param newValue the present value of the property
*/
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
// Report to PropertyChangeListeners.
if (propertyChangeSupport != null) {
propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
// Report to ApplicationInstance.
// The ApplicationInstance is notified directly in order to reduce
// per-Component-instance memory requirements, i.e., it enables the
// PropertyChangeSupport object to only be instantiated on Components
// that have ProperyChangeListeners registered by a third party.
if (applicationInstance != null) {
applicationInstance.notifyComponentPropertyChange(this, propertyName, oldValue, newValue);
}
}
/**
* Returns the value of the specified property.
* This method is generally used only internally by a
* Component
, however there are exceptions.
* The more specific getXXX()
methods to retrieve
* property values from a Component
whenever
* possible.
* See the class-level documentation for a more detailed
* explanation of the use of this method.
*
* @param propertyName the property name
* @return the property value
*/
public final Object get(String propertyName) {
return localStyle.get(propertyName);
}
/**
* Returns the ApplicationInstance
to which this
* Component
is registered, or null if it is not currently
* registered.
*
* @return the application instance
*/
public ApplicationInstance getApplicationInstance() {
return applicationInstance;
}
/**
* Returns the default/base background color of the Component
.
* This property may not be relevant to certain components, though
* even in such cases may be useful for setting a default for
* children.
*
* @return the background color
*/
public Color getBackground() {
return (Color) localStyle.get(PROPERTY_BACKGROUND);
}
/**
* Returns the n
th immediate child component.
*
* @param n the index of the Component
to retrieve
* @return the Component
at index n
* @throws IndexOutOfBoundsException when the index is invalid
*/
public final Component getComponent(int n) {
if (children == null) {
throw new IndexOutOfBoundsException();
}
return (Component) children.get(n);
}
/**
* Recursively searches for the component with the specified id
* by querying this component and its descendants.
* The id value is that retrieved and set via the getId()
* and setId()
methods. This method is in no way
* related to renderId
s.
*
* @param id the user-defined id of the component to be retrieved
* @return the component with the specified id, if it either is this
* component or is a descendant of it, or null otherwise
*/
public final Component getComponent(String id) {
if (id.equals(this.id)) {
return this;
}
if (children == null) {
return null;
}
Iterator it = children.iterator();
while (it.hasNext()) {
Component testComponent = (Component) it.next();
Component targetComponent = testComponent.getComponent(id);
if (targetComponent != null) {
return targetComponent;
}
}
return null;
}
/**
* Returns the number of immediate child Component
s.
*
* @return the number of immediate child Component
s
*/
public final int getComponentCount() {
if (children == null) {
return 0;
} else {
return children.size();
}
}
/**
* Returns an array of all immediate child Component
s.
*
* @return an array of all immediate child Component
s
*/
public final Component[] getComponents() {
if (children == null) {
return EMPTY_COMPONENT_ARRAY;
} else {
return (Component[]) children.toArray(new Component[children.size()]);
}
}
/**
* Returns the local EventListenerList
.
* The listener list is lazily created; invoking this method will
* create the EventListenerList
if required.
*
* @return the listener list
*/
protected EventListenerList getEventListenerList() {
if (listenerList == null) {
listenerList = new EventListenerList();
}
return listenerList;
}
/**
* Returns the next focusable component.
*
* @return the renderId of the next focusable component
* @see #setFocusNextId
*/
public String getFocusNextId() {
return focusNextId;
}
/**
* Returns the previous focusable component.
*
* @return the renderId of the previous focusable component
* @see #setFocusPreviousId
*/
public String getFocusPreviousId() {
return focusPreviousId;
}
/**
* Returns the default/base font of the component.
* This property may not be relevant to certain components, though
* even in such cases may be useful for setting a default for
* children.
*
* @return the font
*/
public Font getFont() {
return (Font) localStyle.get(PROPERTY_FONT);
}
/**
* Returns the default/base foreground color of the Component
.
* This property may not be relevant to certain components, though
* even in such cases may be useful for setting a default for
* children.
*
* @return the foreground color
*/
public Color getForeground() {
return (Color) localStyle.get(PROPERTY_FOREGROUND);
}
/**
* Returns the user-defined identifier of the Component
.
* Note that the user defined identifier has no relation to the
* renderId
.
*
* @return the user-defined identifier
*/
public String getId() {
return id;
}
/**
* Returns the value of the specified indexed property.
* This method is generally used only internally by a
* Component
, however there are exceptions.
* The more specific getXXX()
methods to retrieve
* property values from a Component
whenever
* possible.
* See the class-level documentation for a more detailed
* explanation of the use of this method.
*
* @param propertyName the property name
* @param propertyIndex the property index
* @return the property value
*/
public final Object getIndex(String propertyName, int propertyIndex) {
return localStyle.getIndex(propertyName, propertyIndex);
}
/**
* Returns the LayoutData
object used to describe how this
* Component
should be laid out within its parent container.
*
* @return the layout data, or null if unset
* @see LayoutData
*/
public LayoutData getLayoutData() {
return (LayoutData) localStyle.get(PROPERTY_LAYOUT_DATA);
}
/**
* Returns the specific layout direction setting of this component, if any.
* This method will return null unless a LayoutDirection
is
* specifically set on this Component
.
*
* @return the layout direction property of this
* Component
*/
public LayoutDirection getLayoutDirection() {
return layoutDirection;
}
/**
* Returns the specific locale setting of this component, if any.
* This method will return null unless a Locale
is
* specifically set on this Component
.
*
* @return the locale property of this
* Component
*/
public Locale getLocale() {
return locale;
}
/**
* Returns the Style
object in which local style
* properties are stored. Access to this object is provided
* solely for the purpose of allowing the enabling the application
* container to render the state of the component to a client.
*
* @return the local Style
*/
public Style getLocalStyle() {
return localStyle;
}
/**
* Returns the parent component.
*
* @return the parent component, or null if this component has no parent
*/
public final Component getParent() {
return parent;
}
/**
* Returns the render id of this component.
* This id is only guaranteed to be unique within
* the ApplicationInstance
to which this component is
* registered. This method returns null in the event that the
* component is not registered to an ApplicationInstance
.
*
* @return the ApplicationInstance
-wide unique id of this
* component
* @see nextapp.echo.app.RenderIdSupport#getRenderId()
*/
public String getRenderId() {
return renderId;
}
/**
* Determines the "rendered state" of an indexed property.
* The rendered state is determined by first determining if the given
* property is locally set on this Component
, and returning
* it in that case. If the property state is not set locally, the
* shared Style
assigned to this component will be queried
* for the property value. If the property state is not set in the
* shared Style
, the StyleSheet
of the
* ApplicationInstance
to which this Component
* is registered will be queried for the property value.
* In the event the property is not set in any of these resources,
* null is returned.
*
* The application container will invoke this method
* rather than individual property getter methods to determine the state
* of properties when rendering.
*
* @param propertyName the name of the property
* @return the rendered property value
*/
public final Object getRenderIndexedProperty(String propertyName, int propertyIndex) {
return getRenderIndexedProperty(propertyName, propertyIndex, null);
}
/**
* Determines the "rendered state" of an indexed property.
* The rendered state is determined by first determining if the given
* property is locally set on this Component
, and returning
* it in that case. If the property state is not set locally, the
* shared Style
assigned to this component will be queried
* for the property value. If the property state is not set in the
* shared Style
, the StyleSheet
of the
* ApplicationInstance
to which this Component
* is registered will be queried for the property value.
* In the event the property is not set in any of these resources,
* defaultValue
is returned.
*
* @param propertyName the name of the property
* @param defaultValue the value to be returned if the property is not set
* @return the property state
*/
public final Object getRenderIndexedProperty(String propertyName, int propertyIndex, Object defaultValue) {
if (localStyle.isIndexedPropertySet(propertyName, propertyIndex)) {
// Return local style value.
return localStyle.getIndex(propertyName, propertyIndex);
} else if (sharedStyle != null && sharedStyle.isIndexedPropertySet(propertyName, propertyIndex)) {
// Return style value specified in shared style.
return sharedStyle.getIndex(propertyName, propertyIndex);
} else {
if (applicationInstance != null) {
Style applicationStyle = applicationInstance.getStyle(getClass(), styleName);
if (applicationStyle != null && applicationStyle.isIndexedPropertySet(propertyName, propertyIndex)) {
// Return style value specified in application.
return applicationStyle.getIndex(propertyName, propertyIndex);
}
}
return defaultValue;
}
}
/**
* Returns the rendered Locale
of the Component
.
* If this Component
does not itself specify a locale, its
* ancestors will be queried recursively until a Component
* providing a Locale
is found. If no ancestors have
* Locale
s set, the ApplicationInstance
's
* locale will be returned. In the event that no locale information is
* available from the ancestral hierarchy of Component
s and
* no ApplicationInstance
is registered, null is returned.
*
* @return the locale for this component
*/
public final Locale getRenderLocale() {
if (locale == null) {
if (parent == null) {
if (applicationInstance == null) {
return null;
} else {
return applicationInstance.getLocale();
}
} else {
return parent.getRenderLocale();
}
} else {
return locale;
}
}
/**
* Determines the "rendered state" of a property.
* The rendered state is determined by first determining if the given
* property is locally set on this Component
, and returning
* it in that case. If the property state is not set locally, the
* shared Style
assigned to this component will be queried
* for the property value. If the property state is not set in the
* shared Style
, the StyleSheet
of the
* ApplicationInstance
to which this Component
* is registered will be queried for the property value.
* In the event the property is not set in any of these resources,
* null is returned.
*
* The application container will invoke this method
* rather than individual property getter methods to determine the state
* of properties when rendering.
*
* @param propertyName the name of the property
* @return the rendered property value
*/
public final Object getRenderProperty(String propertyName) {
return getRenderProperty(propertyName, null);
}
/**
* Determines the "rendered state" of a property.
* The rendered state is determined by first determining if the given
* property is locally set on this Component
, and returning
* it in that case. If the property state is not set locally, the
* shared Style
assigned to this component will be queried
* for the property value. If the property state is not set in the
* shared Style
, the StyleSheet
of the
* ApplicationInstance
to which this Component
* is registered will be queried for the property value.
* In the event the property is not set in any of these resources,
* defaultValue
is returned.
*
* @param propertyName the name of the property
* @param defaultValue the value to be returned if the property is not set
* @return the property state
*/
public final Object getRenderProperty(String propertyName, Object defaultValue) {
Object propertyValue = localStyle.get(propertyName);
if (propertyValue != null) {
return propertyValue;
}
if (sharedStyle != null) {
propertyValue = sharedStyle.get(propertyName);
if (propertyValue != null) {
return propertyValue;
}
}
if (applicationInstance != null) {
Style applicationStyle = applicationInstance.getStyle(getClass(), styleName);
if (applicationStyle != null) {
// Return style value specified in application.
propertyValue = applicationStyle.get(propertyName);
if (propertyValue != null) {
return propertyValue;
}
}
}
return defaultValue;
}
/**
* Returns the shared Style
object assigned to this
* Component
.
* As its name implies, the shared Style
* may be shared amongst multiple Component
s.
* Style properties will be rendered from the specified Style
* when they are not specified locally in the Component
* itself.
*
* @return the shared Style
*/
public final Style getStyle() {
return sharedStyle;
}
/**
* Returns the name of the Style
in the
* ApplicationInstance
'sStyleSheet
from
* which the renderer will retrieve properties. The renderer will only query
* the StyleSheet
when properties are not specified directly
* by the Component
or by the Component
's
* shared Style
.
*
* @return the style name
*/
public final String getStyleName() {
return styleName;
}
/**
* Returns the n
th immediate visible
* child Component
.
*
* @param n the index of the Component
to retrieve
* @return the Component
at index n
* @throws IndexOutOfBoundsException when the index is invalid
*/
public final Component getVisibleComponent(int n) {
if (children == null) {
throw new IndexOutOfBoundsException(Integer.toString(n));
}
int visibleComponentCount = 0;
Component component = null;
Iterator it = children.iterator();
while (visibleComponentCount <= n) {
if (!it.hasNext()) {
throw new IndexOutOfBoundsException(Integer.toString(n));
}
component = (Component) it.next();
if (component.isVisible()) {
++visibleComponentCount;
}
}
return component;
}
/**
* Returns the number of visible immediate child
* Component
s.
*
* @return the number of visible immediate child
* Component
s
*/
public final int getVisibleComponentCount() {
if (children == null) {
return 0;
} else {
int visibleComponentCount = 0;
Iterator it = children.iterator();
while (it.hasNext()) {
Component component = (Component) it.next();
if (component.isVisible()) {
++visibleComponentCount;
}
}
return visibleComponentCount;
}
}
/**
* Returns an array of all visible immediate child
* Component
s.
*
* @return an array of all visible immediate child
* Component
s
*/
public final Component[] getVisibleComponents() {
if (children == null) {
return EMPTY_COMPONENT_ARRAY;
} else {
Iterator it = children.iterator();
List visibleChildList = new ArrayList();
while (it.hasNext()) {
Component component = (Component) it.next();
if (component.isVisible()) {
visibleChildList.add(component);
}
}
return (Component[]) visibleChildList.toArray(new Component[visibleChildList.size()]);
}
}
/**
* Determines if a local EventListenerList
exists.
* If no listener list exists, it can be assured that there are thus no
* listeners registered to it. This method should be invoked by event
* firing code prior to invoking getListenerList()
to avoid
* unnecessary creation of an EventListenerList
in response
* to their query.
*
* @return true if a local EventListenerList
exists
*/
protected boolean hasEventListenerList() {
return listenerList != null;
}
/**
* Determines the index of the given Component
within the
* children of this Component
. If the given
* Component
is not a child, -1
is returned.
*
* @param c the Component
to analyze
* @return the index of the specified Component
amongst the
* children of this Component
*/
public final int indexOf(Component c) {
return children == null ? -1 : children.indexOf(c);
}
/**
* Life-cycle method invoked when the Component
is added
* to a registered hierarchy. Implementations should always invoke
* super.init()
.
* Modifications to the component hierarchy are not allowed within this
* method.
*/
public void init() { }
/**
* Determines if this Component
is or is an ancestor of
* the specified Component
.
*
* @param c the Component
to test for ancestry
* @return true if this Component
is an ancestor of the
* specified Component
*/
public final boolean isAncestorOf(Component c) {
while (c != null && c != this) {
c = c.parent;
}
return c == this;
}
/**
* Determines the enabled state of this Component
.
* DisabledComponent
s are not eligible to receive user input.
* The application container may render disabled components with an altered
* appearance.
*
* @return true if the component is enabled
* @see #verifyInput(java.lang.String, java.lang.Object)
*/
public final boolean isEnabled() {
return (flags & FLAG_ENABLED) != 0;
}
/**
* @deprecated use focusNextId/focusPreviousId in (rare) scenario where focus of a specific component is to be avoided.
* This method will be removed soon.
*/
public boolean isFocusTraversalParticipant() { return false; }
/**
* Determines if the Component
is registered to an
* ApplicationInstance
.
*
* @return true if the Component
is registered to an
* ApplicationInstance
*/
public final boolean isRegistered() {
return applicationInstance != null;
}
/**
* Determines whether this Component
should be rendered with
* an enabled state.
* DisabledComponent
s are not eligible to receive user input.
* The application container may render disabled components with an altered
* appearance.
*
* @return true if the component should be rendered enabled.
*/
public final boolean isRenderEnabled() {
Component component = this;
while (component != null) {
if ((component.flags & FLAG_ENABLED) == 0) {
return false;
}
component = component.parent;
}
return true;
}
/**
* Determines if the Component
and all of its parents are
* visible.
*
* @return true if the Component
is recursively visible
*/
public final boolean isRenderVisible() {
Component component = this;
while (component != null) {
if ((component.flags & FLAG_VISIBLE) == 0) {
return false;
}
component = component.parent;
}
return true;
}
/**
* Determines if a given Component
is valid to be added as a
* child to this Component
. Default implementation always
* returns true, may be overridden to provide specific behavior.
*
* @param child the Component
to evaluate as a child
* @return true if the Component
is a valid child
*/
public boolean isValidChild(Component child) {
return true;
}
/**
* Determines if this Component
is valid to be added as a
* child of the given parent Component
. Default
* implementation always returns true, may be overridden to provide specific
* behavior.
*
* @param parent the Component
to evaluate as a parent
* @return true if the Component
is a valid parent
*/
public boolean isValidParent(Component parent) {
return true;
}
/**
* Returns the visibility state of this Component
.
* Non-visible components will not be seen by the rendering application
* container, and will not be rendered in any fashion on the user
* interface. Rendering Application Containers should ensure that no
* information about the state of an invisible component is provided to
* the user interface for security purposes.
*
* @return the visibility state of this Component
*/
public final boolean isVisible() {
return (FLAG_VISIBLE & flags) != 0;
}
/**
* Processes client input specific to the Component
* received from the UpdateManager
.
* Derivative implementations should take care to invoke
* super.processInput()
.
*
* Security note: Because input to this method is
* likely from a remote client, it should be treated as potentially hostile.
* All input to this method should be carefully verified.
* For example, directly invoking set()
method with the
* provided input would constitute a security hole.
*
* @param inputName the name of the input
* @param inputValue the value of the input
* @see nextapp.echo.app.update.UpdateManager
*/
public void processInput(String inputName, Object inputValue) { }
/**
* Sets the ApplicationInstance
to which this component is
* registered.
*
* The ApplicationInstance
to which a component is registered
* may not be changed directly from one to another, i.e., if the component
* is registered to instance "A" and an attempt is made to set it to
* instance "B", an IllegalStateException
will be thrown. In
* order to change the instance to which a component is registered, the
* instance must first be set to null.
*
* @param newValue the new ApplicationInstance
* @throws IllegalStateException in the event that an attempt is made to
* re-add a Component
to a hierarchy during a
* dispose()
operation or if an attempt is made to
* remove a Component
during an init()
* operation.
*/
void register(ApplicationInstance newValue) {
// Verifying 'registering' flag is not set.
if ((flags & FLAG_REGISTERING) != 0) {
throw new IllegalStateException(
"Illegal attempt to register/unregister Component from within invocation of registration change " +
"life-cycle method.");
}
try {
// Set 'registering' flag.
flags |= FLAG_REGISTERING;
if (applicationInstance == newValue) {
// Child component added/removed during init()/dispose(): do nothing.
return;
}
if (applicationInstance != null && newValue != null) {
throw new IllegalStateException(
"Illegal attempt to re-register Component to alternate ApplicationInstance.");
}
if (newValue == null) { // unregistering
if (children != null) {
Iterator it = children.iterator();
while (it.hasNext()) {
((Component) it.next()).register(null); // Recursively unregister children.
}
}
applicationInstance.unregisterComponent(this);
}
applicationInstance = newValue;
if (newValue != null) { // registering
applicationInstance.registerComponent(this);
if (children != null) {
Iterator it = children.iterator();
while (it.hasNext()) {
((Component) it.next()).register(newValue); // Recursively register children.
}
}
}
} finally {
// Clear 'registering' flag.
flags &= ~FLAG_REGISTERING;
}
}
/**
* Removes the specified child Component
from this
* Component
.
*
* All Component
remove operations use this method to
* remove Component
s. Component
s that require
* notification of all child removals should
* override this method (while ensuring to call the superclass'
* implementation).
*
* @param c the child Component
to remove
*/
public void remove(Component c) {
if (children == null || !children.contains(c)) {
// Do-nothing if component is not a child.
return;
}
c.doDispose();
// Deregister child.
if (applicationInstance != null) {
c.register(null);
}
// Dissolve references between parent and child.
children.remove(c);
c.parent = null;
// Notify PropertyChangeListeners of change.
firePropertyChange(CHILDREN_CHANGED_PROPERTY, c, null);
}
/**
* Removes the Component
at the n
th index.
*
* @param n the index of the child Component
to remove
* @throws IndexOutOfBoundsException if the index is not valid
*/
public void remove(int n) {
if (children == null) {
throw new IndexOutOfBoundsException();
}
remove(getComponent(n));
}
/**
* Removes all child Component
s.
*/
public void removeAll() {
if (children != null) {
while (children.size() > 0) {
Component c = (Component) children.get(children.size() - 1);
remove(c);
}
children = null;
}
}
/**
* Removes a property change listener from this Component
.
*
* @param l the listener to be removed
*/
public void removePropertyChangeListener(PropertyChangeListener l) {
if (propertyChangeSupport != null) {
propertyChangeSupport.removePropertyChangeListener(l);
}
}
/**
* Removes a property change listener from this Component
for a specific property.
*
* @param propertyName the name of the property for which to listen
* @param l the listener to be removed
*/
public void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
if (propertyChangeSupport != null) {
propertyChangeSupport.removePropertyChangeListener(propertyName, l);
}
}
/**
* Sets a generic property of the Component
.
* The value will be stored in this Component
's local style.
*
* @param propertyName the name of the property
* @param newValue the value of the property
* @see #get(java.lang.String)
*/
public void set(String propertyName, Object newValue) {
Object oldValue = localStyle.get(propertyName);
localStyle.set(propertyName, newValue);
firePropertyChange(propertyName, oldValue, newValue);
}
/**
* Sets the default background color of the Component
.
*
* @param newValue the new background Color
*/
public void setBackground(Color newValue) {
set(PROPERTY_BACKGROUND, newValue);
}
/**
* Sets the child components for this container, removing
* any existing children.
* @param components the child components for this container.
*/
public void setComponents(Component[] components) {
removeAll();
for (int i = 0; i < components.length; i++) {
add(components[i]);
}
}
/**
* Sets the enabled state of the Component
.
*
* @param newValue the new state
* @see #isEnabled
*/
public void setEnabled(boolean newValue) {
boolean oldValue = (flags & FLAG_ENABLED) != 0;
if (oldValue != newValue) {
flags ^= FLAG_ENABLED; // Toggle FLAG_ENABLED bit.
firePropertyChange(ENABLED_CHANGED_PROPERTY, new Boolean(oldValue), new Boolean(newValue));
}
}
/**
* @deprecated use focusNextId/focusPreviousId in (rare) scenario where focus of a specific component is to be avoided.
* This method will be removed soon.
*/
public void setFocusTraversalParticipant(boolean newValue) { }
/**
* Sets the next focusable component.
* RenderIds are used for setting focus traversal order in order to avoid referencing/garbage collection issues.
* Ensure that the component has a renderId (either by way of it having been registered with the application, or
* having it manually set).
*
* @param newValue the renderId
of the next focusable component
*/
public void setFocusNextId(String newValue) {
String oldValue = focusNextId;
focusNextId = newValue;
firePropertyChange(FOCUS_NEXT_ID_CHANGED_PROPERTY, oldValue, newValue);
}
/**
* Sets the previous focusable component.
* RenderIds are used for setting focus traversal order in order to avoid referencing/garbage collection issues.
* Ensure that the component has a renderId (either by way of it having been registered with the application, or
* having it manually set).
*
* @param newValue the renderId
of the previous focusable component
*/
public void setFocusPreviousId(String newValue) {
String oldValue = focusPreviousId;
focusPreviousId = newValue;
firePropertyChange(FOCUS_PREVIOUS_ID_CHANGED_PROPERTY, oldValue, newValue);
}
/**
* Sets the default text font of the Component
.
*
* @param newValue the new Font
*/
public void setFont(Font newValue) {
set(PROPERTY_FONT, newValue);
}
/**
* Sets the default foreground color of the Component
.
*
* @param newValue the new foreground Color
*/
public void setForeground(Color newValue) {
set(PROPERTY_FOREGROUND, newValue);
}
/**
* Sets a user-defined identifier for this Component
.
*
* @param id the new identifier
*/
public void setId(String id) {
this.id = id;
}
/**
* Sets a generic indexed property of the Component
.
* The value will be stored in this Component
's local style.
*
* @param propertyName the name of the property
* @param propertyIndex the index of the property
* @param newValue the value of the property
*
* @see #getIndex(java.lang.String, int)
*/
public void setIndex(String propertyName, int propertyIndex, Object newValue) {
localStyle.setIndex(propertyName, propertyIndex, newValue);
firePropertyChange(propertyName, null, null);
}
/**
* Sets the LayoutData
of this Component
.
* A LayoutData
implementation describes how this
* Component
is laid out within/interacts with its
* containing parent Component
.
*
* @param newValue the new LayoutData
* @see LayoutData
*/
public void setLayoutData(LayoutData newValue) {
set(PROPERTY_LAYOUT_DATA, newValue);
}
/**
* Sets the LayoutDirection
of this Component
,
* describing whether content is rendered left-to-right or right-to-left.
*
* @param newValue the new LayoutDirection
.
*/
public void setLayoutDirection(LayoutDirection newValue) {
LayoutDirection oldValue = layoutDirection;
layoutDirection = newValue;
firePropertyChange(LAYOUT_DIRECTION_CHANGED_PROPERTY, oldValue, newValue);
}
/**
* Sets the locale of the Component
.
*
* @param newValue the new locale
* @see #getLocale()
*/
public void setLocale(Locale newValue) {
Locale oldValue = locale;
locale = newValue;
firePropertyChange(LOCALE_CHANGED_PROPERTY, oldValue, newValue);
}
/**
* Sets a custom render identifier for this Component
.
* The identifier may be changed without notification if another
* component is already using it.
* Identifiers are limited to ASCII alphanumeric values.
* The first character must be an upper- or lower-case ASCII letter.
* Underscores and other punctuation characters are not permitted.
* Use of "TitleCase" or "camelCase" is recommended.
*
* @param renderId the new identifier
*/
public void setRenderId(String renderId) {
if (this.renderId != null && renderId != null && this.applicationInstance != null) {
throw new IllegalStateException("Cannot set renderId while component is registered.");
}
if (renderId != null) {
int length = renderId.length();
if (!isRenderIdStart(renderId.charAt(0))) {
throw new IllegalArgumentException("Invalid identifier:" + renderId);
}
for (int i = 1; i < length; ++i) {
if (!isRenderIdPart(renderId.charAt(i))) {
throw new IllegalArgumentException("Invalid identifier:" + renderId);
}
}
}
assignRenderId(renderId);
}
/**
* Sets the shared style of the Component
.
* Setting the shared style will have no impact on the local stylistic
* properties of the Component
.
*
* @param newValue the new shared style
* @see #getStyle()
*/
public void setStyle(Style newValue) {
Style oldValue = sharedStyle;
sharedStyle = newValue;
firePropertyChange(STYLE_CHANGED_PROPERTY, oldValue, newValue);
}
/**
* Sets the name of the style to use from the
* ApplicationInstance
-defined StyleSheet
.
* Setting the style name will have no impact on the local stylistic
* properties of the Component
.
*
* @param newValue the new style name
* @see #getStyleName
*/
public void setStyleName(String newValue) {
String oldValue = styleName;
styleName = newValue;
firePropertyChange(STYLE_NAME_CHANGED_PROPERTY, oldValue, newValue);
}
/**
* Sets the visibility state of this Component
.
*
* @param newValue the new visibility state
* @see #isVisible()
*/
public void setVisible(boolean newValue) {
boolean oldValue = (flags & FLAG_VISIBLE) != 0;
if (oldValue != newValue) {
flags ^= FLAG_VISIBLE; // Toggle FLAG_VISIBLE bit.
firePropertyChange(VISIBLE_CHANGED_PROPERTY, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
if (parent != null) {
parent.firePropertyChange(CHILD_VISIBLE_CHANGED_PROPERTY, newValue ? null : this, newValue ? this : null);
}
}
}
/**
* A life-cycle method invoked before the component is rendered to ensure it
* is in a valid state. Default implementation is empty. Overriding
* implementations should ensure to invoke super.validate()
* out of convention.
*/
public void validate() { }
/**
* Invoked by the ClientUpdateManager
on each component in the
* hierarchy whose processInput()
method will layer be invoked
* in the current transaction. This method should return true if the
* component will be capable of processing the given input in its current
* state or false otherwise. This method should not do any of the actual
* processing work if overridden (any actual processing should be done in
* the processInput()
implementation).
*
* The default implementation verifies that the component is visible,
* enabled, and not "obscured" by the presence of any modal component.
* If overriding this method, your implementation should invoke
* super.verifyInput()
.
*
* @param inputName the name of the input
* @param inputValue the value of the input
* @return true if the input is allowed to be processed by this component
* in its current state
*/
public boolean verifyInput(String inputName, Object inputValue) {
if (applicationInstance != null && !applicationInstance.verifyModalContext(this)) {
return false;
}
return isVisible() && isEnabled();
}
/**
* Determines the index of the given Component
within the
* visible children of this Component
. If the
* given Component
is not a child, -1
is
* returned.
*
* @param c the Component
to analyze
* @return the index of the specified Component
amongst the
* visible children of this Component
*/
public final int visibleIndexOf(Component c) {
if (!c.isVisible()) {
return -1;
}
if (children == null) {
return -1;
}
int visibleIndex = 0;
Iterator it = children.iterator();
while (it.hasNext()) {
Component component = (Component) it.next();
if (!component.isVisible()) {
continue;
}
if (component.equals(c)) {
return visibleIndex;
}
++visibleIndex;
}
return -1;
}
}