/* * 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.echo2.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.echo2.app.event.EventListenerList; /** * A representation of an Echo component. This is an abstract base class from * which all Echo components are derived. * <p> * A hierarchy of <code>Component</code> objects is used to represent the * state of an application's user interface. A <code>Component</code> may have * a single parent <code>Component</code> and may contain zero or more child * <code>Component</code>s. Certain <code>Component</code>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 <code>Component</code>s * they may be added to. In the event that an application attempts to add a * child <code>Component</code> to a parent <code>Component</code> in spite * of these requirements, an <code>IllegalChildException</code> is thrown. * * <h3>Properties and Styles</h3> * <p> * The state of a single <code>Component</code> 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. * <p> * "Style Properties" have a special definition because they may be stored in * <code>Style</code> or <code>StyleSheet</code> objects instead of as * properties of a specific <code>Component</code> instance. Property values * contained in a relevant <code>Style</code> or <code>StyleSheet</code> * will be used for rendering when the property values are not specified by a * <code>Component</code> itself. Style properties are identified by the * presence of a public static constant name in a <code>Component</code> * implementation with the prefix <code>PROPERTY_</code>. In the base * <code>Component</code> class itself there are several examples of style * properties, such as <code>PROPERTY_BACKGROUND</code>,<code>PROPERTY_FONT</code> * and <code>PROPERTY_LAYOUT_DATA</code>. The rendering application container * will use the <code>Component.getRenderProperty()</code> and * <code>Component.getRenderIndexedProperty()</code> to retrieve the values of * stylistic properties, in order that their values might be obtained from * the <code>Component</code>'s shared <code>Style</code> or the * <code>ApplicationInstance</code>'s <code>StyleSheet</code> in the event * they are not directly set in the <code>Component</code>. * <p> * A <code>Component</code> implementation should not store the values of * style properties as instance variables. Rather, the values of style * properties should be stored in the local <code>Style</code> instance, by * way of the <code>setProperty()</code> method. The * <code>getProperty()</code> 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 * <code>getProperty()</code>/<code>setProperty()</code> interface. * * <h3>Events</h3> * <p> * Many <code>Component</code>s will provide the capability to register * <code>EventListener</code>s to notify interested parties when various * state changes occur. The base <code>Component</code> class provides an * <code>EventListenerList</code> as a convenient and memory efficient means * of storing such listeners. The internal <code>EventListenerList</code> may * be obtained using the <code>getEventListenerList()</code> method. The * <code>EventListenerList</code> is lazy-created and will only be * instantiated on the first invocation of the * <code>getEventListenerList()</code> method. If the intent is only to * inquire about the state of event listeners without necessarily forcing * instantiation of an <code>EventListenerList</code>, the * <code>hasEventListenerList()</code> should be queried prior to invoking * <code>getEventListenerList()</code>. */ public abstract class Component implements RenderIdSupport, Serializable { /** * <code>ArrayList</code> capacity for child storage. */ private static final int CHILD_LIST_CAPACITY = 3; /** * Empty array returned by <code>getComponents()</code> when a * <code>Component</code> has no children. */ private static final Component[] EMPTY_COMPONENT_ARRAY = new Component[0]; /** * Flag indicating the <code>Component</code> is enabled. */ private static final int FLAG_ENABLED = 0x1; /** * Flag indicating the <code>Component</code> is visible. */ private static final int FLAG_VISIBLE = 0x2; /** * Flag indicating the <code>Component</code> will participate in the * focus traversal order. */ private static final int FLAG_FOCUS_TRAVERSAL_PARTICIPANT= 0x4; /** * Flag indicating that the <code>Component</code> is currently undergoing * registration to an <code>ApplicationInstance</code>. */ private static final int FLAG_REGISTERING = 0x8; private static final int FLAG_INIT_IN_PROGRESS = 0x10; private static final int FLAG_DISPOSE_IN_PROGRESS = 0x20; private static final int FLAG_INITIALIZED = 0x40; /** * Flag mask of bits used for storage of focus traversal index. */ private static final int FLAGS_FOCUS_TRAVERSAL_INDEX = 0x7fff0000; public static final String CHILDREN_CHANGED_PROPERTY = "children"; public static final String ENABLED_CHANGED_PROPERTY = "enabled"; public static final String FOCUS_TRAVERSAL_INDEX_CHANGED_PROPERTY = "focusTraversalIndex"; public static final String FOCUS_TRAVERSAL_PARTICIPANT_CHANGED_PROPERTY = "focusTraversalParticipant"; public static final String LAYOUT_DIRECTION_CHANGED_PROPERTY = "layoutDirection"; public static final String LOCALE_CHANGED_PROPERTY = "locale"; 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"; public static final String STYLE_CHANGED_PROPERTY = "style"; public static final String STYLE_NAME_CHANGED_PROPERTY = "styleName"; public static final String VISIBLE_CHANGED_PROPERTY = "visible"; /** * Determines if a character is valid as the non-first character of a renderId. * * @param ch the character * @return true if it is valid */ private static final boolean isRenderIdPart(char ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'); } /** * Determines if a character is valid as the first character of a renderId. * * @param ch the character * @return true if it is valid */ private static final boolean isRenderIdStart(char ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); } /** The <code>ApplicationInstance</code> 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, visibility, * focus traversal participation, and focus traversal index. * Multiple booleans are wrapped in a single integer * to save memory, since many <code>Component</code>instances will be * created. */ private int flags; /** * A user-defined identifier for this component. * This identifier is not related in any way to <code>renderId</code>. */ private String id; /** * The layout direction of the component. * This property is generally unset, as layout direction information is * normally inherited from the <code>ApplicationInstance</code> or from * an ancestor <code>Component</code> 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 <code>ApplicationInstance</code> or from an ancestor * <code>Component</code> 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 <code>id</code>. */ private String renderId; /** Shared style. */ private Style sharedStyle; /** Name of style to use from application style sheet */ private String styleName; /** * Creates a new <code>Component</code>. */ public Component() { super(); flags = FLAG_ENABLED | FLAG_VISIBLE | FLAG_FOCUS_TRAVERSAL_PARTICIPANT; localStyle = new MutableStyle(); } /** * Adds the specified <code>Component</code> as a child of this * <code>Component</code>. The child will be added at the greatest * index. * * @param c the child <code>Component</code> to add */ public void add(Component c) { add(c, -1); } /** * Adds the specified <code>Component</code> as the <code>n</code>th * child of this component. * All component-add operations use this method to add components. * <code>Component</code>s that require notification of all child additions * should override this method (making sure to call the superclass' * implementation). * * @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 <code>Component</code>. * * @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 <code>Component</code> 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 the<code>Component</code>. * This method is invoked by the <code>ApplicationInstance</code> * when the component is registered or unregistered, or by manual * invocation of <code>setRenderId()</code>. This method performs no * error checking. * * @param renderId the new identifier * @see #getRenderId() * @see #setRenderId(String) */ void assignRenderId(String renderId) { this.renderId = renderId; } /** * Life-cycle method invoked when the <code>Component</code> is removed * from a registered hierarchy. Implementations should always invoke * <code>super.dispose()</code>. * Modifications to the component hierarchy are not allowed within this * method. */ public void dispose() { } /** * Recursively executes the <code>dispose()</code> life-cycle methods of * this <code>Component</code> 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 <code>init()</code> life-cycle methods of * this <code>Component</code> 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 <code>PropertyChangeListener</code>s * and to the <code>ApplicationInstance</code>'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 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); } // Report to PropertyChangeListeners. if (propertyChangeSupport != null) { propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } } /** * Returns the <code>ApplicationInstance</code> to which this * <code>Component</code> 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 <code>Component</code>. * 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.getProperty(PROPERTY_BACKGROUND); } /** * Returns the <code>n</code>th immediate child component. * * @param n the index of the <code>Component</code> to retrieve * @return the <code>Component</code> at index <code>n</code> * @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 <code>getId()</code> * and <code>setId()</code> methods. This method is in no way * related to <code>renderId</code>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 <code>Component</code>s. * * @return the number of immediate child <code>Component</code>s */ public final int getComponentCount() { if (children == null) { return 0; } else { return children.size(); } } /** * Returns an array of all immediate child <code>Component</code>s. * * @return an array of all immediate child <code>Component</code>s */ public final Component[] getComponents() { if (children == null) { return EMPTY_COMPONENT_ARRAY; } else { return (Component[]) children.toArray(new Component[children.size()]); } } /** * Returns the local <code>EventListenerList</code>. * The listener list is lazily created; invoking this method will * create the <code>EventListenerList</code> if required. * * @return the listener list */ protected EventListenerList getEventListenerList() { if (listenerList == null) { listenerList = new EventListenerList(); } return listenerList; } /** * Returns the focus traversal (tab) index of the component. * Components with numerically lower indices will be focused before * components with numerically higher indices. The value 0 has special * meaning, in that components with a value of 0 will be focused last. * The default value is 0. * * @return the focus traversal index, a value between 0 and 32767 */ public final int getFocusTraversalIndex() { return (flags & FLAGS_FOCUS_TRAVERSAL_INDEX) >> 16; } /** * 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.getProperty(PROPERTY_FONT); } /** * Returns the default/base foreground color of the <code>Component</code>. * 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.getProperty(PROPERTY_FOREGROUND); } /** * Returns the user-defined identifier of the <code>Component</code>. * Note that the user defined identifier has no relation to the * <code>renderId</code>. * * @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 * <code>Component</code>, however there are exceptions. * The more specific <code>getXXX()</code> methods to retrieve * property values from a <code>Component</code> 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 getIndexedProperty(String propertyName, int propertyIndex) { return localStyle.getIndexedProperty(propertyName, propertyIndex); } /** * Returns the <code>LayoutData</code> object used to describe how this * <code>Component</code> should be laid out within its parent container. * * @return the layout data, or null if unset * @see LayoutData */ public LayoutData getLayoutData() { return (LayoutData) localStyle.getProperty(PROPERTY_LAYOUT_DATA); } /** * Returns the specific layout direction setting of this component, if any. * This method will return null unless a <code>LayoutDirection</code> is * specifically set on <strong>this</strong> <code>Component</code>. * * @return the layout direction property of <strong>this</strong> * <code>Component</code> * @see #getRenderLayoutDirection() */ public LayoutDirection getLayoutDirection() { return layoutDirection; } /** * Returns the specific locale setting of this component, if any. * This method will return null unless a <code>Locale</code> is * specifically set on <strong>this</strong> <code>Component</code>. * * @return the locale property of <strong>this</strong> * <code>Component</code> * @see #getRenderLocale() */ public Locale getLocale() { return locale; } /** * Returns the parent component. * * @return the parent component, or null if this component has no parent */ public final Component getParent() { return parent; } /** * Returns the value of the specified property. * This method is generally used only internally by a * <code>Component</code>, however there are exceptions. * The more specific <code>getXXX()</code> methods to retrieve * property values from a <code>Component</code> 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 getProperty(String propertyName) { return localStyle.getProperty(propertyName); } /** * Returns the render id of this component. * This id is only guaranteed to be unique within * the <code>ApplicationInstance</code> to which this component is * registered. This method returns null in the event that the * component is not registered to an <code>ApplicationInstance</code>. * * @return the <code>ApplicationInstance</code>-wide unique id of this * component * @see nextapp.echo2.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 <code>Component</code>, and returning * it in that case. If the property state is not set locally, the * shared <code>Style</code> assigned to this component will be queried * for the property value. If the property state is not set in the * shared <code>Style</code>, the <code>StyleSheet</code> of the * <code>ApplicationInstance</code> to which this <code>Component</code> * 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. * <p> * 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 <code>Component</code>, and returning * it in that case. If the property state is not set locally, the * shared <code>Style</code> assigned to this component will be queried * for the property value. If the property state is not set in the * shared <code>Style</code>, the <code>StyleSheet</code> of the * <code>ApplicationInstance</code> to which this <code>Component</code> * is registered will be queried for the property value. * In the event the property is not set in any of these resources, * <code>defaultValue</code> 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.getIndexedProperty(propertyName, propertyIndex); } else if (sharedStyle != null && sharedStyle.isIndexedPropertySet(propertyName, propertyIndex)) { // Return style value specified in shared style. return sharedStyle.getIndexedProperty(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.getIndexedProperty(propertyName, propertyIndex); } } return defaultValue; } } /** * Returns the rendered <code>LayoutDirection</code> of the * <code>Component</code>. * * @return the layout direction of this component */ public final LayoutDirection getRenderLayoutDirection() { if (layoutDirection == null) { if (locale == null) { if (parent == null) { if (applicationInstance == null) { return null; } else { return applicationInstance.getLayoutDirection(); } } else { return parent.getRenderLayoutDirection(); } } else { return LayoutDirection.forLocale(locale); } } else { return layoutDirection; } } /** * Returns the rendered <code>Locale</code> of the <code>Component</code>. * If this <code>Component</code> does not itself specify a locale, its * ancestors will be queried recursively until a <code>Component</code> * providing a <code>Locale</code> is found. If no ancestors have * <code>Locale</code>s set, the <code>ApplicationInstance</code>'s * locale will be returned. In the event that no locale information is * available from the ancestral hierarchy of <code>Component</code>s and * no <code>ApplicationInstance</code> 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 <code>Component</code>, and returning * it in that case. If the property state is not set locally, the * shared <code>Style</code> assigned to this component will be queried * for the property value. If the property state is not set in the * shared <code>Style</code>, the <code>StyleSheet</code> of the * <code>ApplicationInstance</code> to which this <code>Component</code> * 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. * <p> * 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 <code>Component</code>, and returning * it in that case. If the property state is not set locally, the * shared <code>Style</code> assigned to this component will be queried * for the property value. If the property state is not set in the * shared <code>Style</code>, the <code>StyleSheet</code> of the * <code>ApplicationInstance</code> to which this <code>Component</code> * is registered will be queried for the property value. * In the event the property is not set in any of these resources, * <code>defaultValue</code> 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.getProperty(propertyName); if (propertyValue != null) { return propertyValue; } if (sharedStyle != null) { propertyValue = sharedStyle.getProperty(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.getProperty(propertyName); if (propertyValue != null) { return propertyValue; } } } return defaultValue; } /** * Returns the shared <code>Style</code> object assigned to this * <code>Component</code>. * As its name implies, the <strong>shared</strong> <code>Style</code> * may be shared amongst multiple <code>Component</code>s. * Style properties will be rendered from the specified <code>Style</code> * when they are not specified locally in the <code>Component</code> * itself. * * @return the shared <code>Style</code> */ public final Style getStyle() { return sharedStyle; } /** * Returns the name of the <code>Style</code> in the * <code>ApplicationInstance</code>'s<code>StyleSheet</code> from * which the renderer will retrieve properties. The renderer will only query * the <code>StyleSheet</code> when properties are not specified directly * by the <code>Component</code> or by the <code>Component</code>'s * shared <code>Style</code>. * * @return the style name */ public final String getStyleName() { return styleName; } /** * Returns the <code>n</code>th immediate <strong>visible</strong> * child <code>Component</code>. * * @param n the index of the <code>Component</code> to retrieve * @return the <code>Component</code> at index <code>n</code> * @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 <strong>visible</strong> immediate child * <code>Component</code>s. * * @return the number of <strong>visible</strong> immediate child * <code>Component</code>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 <strong>visible</strong> immediate child * <code>Component</code>s. * * @return an array of all <strong>visible</strong> immediate child * <code>Component</code>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 <code>EventListenerList</code> 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 <code>getListenerList()</code> to avoid * unnecessary creation of an <code>EventListenerList</code> in response * to their query. * * @return true if a local <code>EventListenerList</code> exists */ protected boolean hasEventListenerList() { return listenerList != null; } /** * Determines the index of the given <code>Component</code> within the * children of this <code>Component</code>. If the given * <code>Component</code> is not a child, <code>-1</code> is returned. * * @param c the <code>Component</code> to analyze * @return the index of the specified <code>Component</code> amongst the * children of this <code>Component</code> */ public final int indexOf(Component c) { return children == null ? -1 : children.indexOf(c); } /** * Life-cycle method invoked when the <code>Component</code> is added * to a registered hierarchy. Implementations should always invoke * <code>super.init()</code>. * Modifications to the component hierarchy are not allowed within this * method. */ public void init() { } /** * Determines if this <code>Component</code> is or is an ancestor of * the specified <code>Component</code>. * * @param c the <code>Component</code> to test for ancestry * @return true if this <code>Component</code> is an ancestor of the * specified <code>Component</code> */ public final boolean isAncestorOf(Component c) { while (c != null && c != this) { c = c.parent; } return c == this; } /** * Determines the enabled state of this <code>Component</code>. * Disabled<code>Component</code>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; } /** * Determines if the <code>Component</code> participates in (tab) focus * traversal. * * @return true if the <code>Component</code> participates in focus * traversal */ public boolean isFocusTraversalParticipant() { return (flags & FLAG_FOCUS_TRAVERSAL_PARTICIPANT) != 0; } /** * Determines if the <code>Component</code> is registered to an * <code>ApplicationInstance</code>. * * @return true if the <code>Component</code> is registered to an * <code>ApplicationInstance</code> */ public final boolean isRegistered() { return applicationInstance != null; } /** * Determines whether this <code>Component</code> should be rendered with * an enabled state. * Disabled<code>Component</code>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 <code>Component</code> and all of its parents are * visible. * * @return true if the <code>Component</code> 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 <code>Component</code> is valid to be added as a * child to this <code>Component</code>. Default implementation always * returns true, may be overridden to provide specific behavior. * * @param child the <code>Component</code> to evaluate as a child * @return true if the <code>Component</code> is a valid child */ public boolean isValidChild(Component child) { return true; } /** * Determines if this <code>Component</code> is valid to be added as a * child of the given parent <code>Component</code>. Default * implementation always returns true, may be overridden to provide specific * behavior. * * @param parent the <code>Component</code> to evaluate as a parent * @return true if the <code>Component</code> is a valid parent */ public boolean isValidParent(Component parent) { return true; } /** * Returns the visibility state of this <code>Component</code>. * 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 <code>Component</code> */ public final boolean isVisible() { return (FLAG_VISIBLE & flags) != 0; } /** * Processes client input specific to the <code>Component</code> * received from the <code>UpdateManager</code>. * Derivative implementations should take care to invoke * <code>super.processInput()</code>. * * @param inputName the name of the input * @param inputValue the value of the input * @see nextapp.echo2.app.update.UpdateManager */ public void processInput(String inputName, Object inputValue) { } /** * Sets the <code>ApplicationInstance</code> to which this component is * registered. * <p> * The <code>ApplicationInstance</code> 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 <code>IllegalStateException</code> 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 <code>ApplicationInstance</code> * @throws IllegalStateException in the event that an attempt is made to * re-add a <code>Component</code> to a hierarchy during a * <code>dispose()</code> operation or if an attempt is made to * remove a <code>Component</code> during an <code>init()</code> * 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 <code>Component</code> from this * <code>Component</code>. * <p> * All <code>Component</code> remove operations use this method to * remove <code>Component</code>s. <code>Component</code>s that require * notification of all child removals should * override this method (while ensuring to call the superclass' * implementation). * * @param c the child <code>Component</code> 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 <code>Component</code> at the <code>n</code>th index. * * @param n the index of the child <code>Component</code> 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 <code>Component</code>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 <code>Component</code>. * * @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 <code>Component</code> 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 the default background color of the <code>Component</code>. * * @param newValue the new background <code>Color</code> */ public void setBackground(Color newValue) { setProperty(PROPERTY_BACKGROUND, newValue); } /** * Sets the enabled state of the <code>Component</code>. * * @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)); } } /** * Sets the focus traversal (tab) index of the component. * * @param newValue the new focus traversal index * @see #getFocusTraversalIndex() */ public void setFocusTraversalIndex(int newValue) { int oldValue = getFocusTraversalIndex(); newValue &= 0x7fff; flags = flags & ((~FLAGS_FOCUS_TRAVERSAL_INDEX)) | (newValue << 16); firePropertyChange(FOCUS_TRAVERSAL_INDEX_CHANGED_PROPERTY, new Integer(oldValue), new Integer(newValue)); } /** * Sets whether the component participates in the focus traversal order * (tab order). * * @param newValue true if the component participates in the focus * traversal order */ public void setFocusTraversalParticipant(boolean newValue) { boolean oldValue = isFocusTraversalParticipant(); if (oldValue != newValue) { flags ^= FLAG_FOCUS_TRAVERSAL_PARTICIPANT; // Toggle FLAG_FOCUS_TRAVERSAL_PARTICIPANT bit. firePropertyChange(FOCUS_TRAVERSAL_PARTICIPANT_CHANGED_PROPERTY, new Boolean(oldValue), new Boolean(newValue)); } } /** * Sets the default text font of the <code>Component</code>. * * @param newValue the new <code>Font</code> */ public void setFont(Font newValue) { setProperty(PROPERTY_FONT, newValue); } /** * Sets the default foreground color of the <code>Component</code>. * * @param newValue the new foreground <code>Color</code> */ public void setForeground(Color newValue) { setProperty(PROPERTY_FOREGROUND, newValue); } /** * Sets a user-defined identifier for this <code>Component</code>. * * @param id the new identifier */ public void setId(String id) { this.id = id; } /** * Sets a generic indexed property of the <code>Component</code>. * The value will be stored in this <code>Component</code>'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 #getIndexedProperty(java.lang.String, int) */ public void setIndexedProperty(String propertyName, int propertyIndex, Object newValue) { localStyle.setIndexedProperty(propertyName, propertyIndex, newValue); firePropertyChange(propertyName, null, null); } /** * Sets the <code>LayoutData</code> of this <code>Component</code>. * A <code>LayoutData</code> implementation describes how this * <code>Component</code> is laid out within/interacts with its * containing parent <code>Component</code>. * * @param newValue the new <code>LayoutData</code> * @see LayoutData */ public void setLayoutData(LayoutData newValue) { setProperty(PROPERTY_LAYOUT_DATA, newValue); } /** * Sets the <code>LayoutDirection</code> of this <code>Component</code>, * describing whether content is rendered left-to-right or right-to-left. * * @param newValue the new <code>LayoutDirection</code>. */ public void setLayoutDirection(LayoutDirection newValue) { LayoutDirection oldValue = layoutDirection; layoutDirection = newValue; firePropertyChange(LAYOUT_DIRECTION_CHANGED_PROPERTY, oldValue, newValue); } /** * Sets the locale of the <code>Component</code>. * * @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 generic property of the <code>Component</code>. * The value will be stored in this <code>Component</code>'s local style. * * @param propertyName the name of the property * @param newValue the value of the property * @see #getProperty(java.lang.String) */ public void setProperty(String propertyName, Object newValue) { Object oldValue = localStyle.getProperty(propertyName); localStyle.setProperty(propertyName, newValue); firePropertyChange(propertyName, oldValue, newValue); } /** * Sets a custom render identifier for this <code>Component</code>. * 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 <code>Component</code>. * Setting the shared style will have no impact on the local stylistic * properties of the <code>Component</code>. * * @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 * <code>ApplicationInstance</code>-defined <code>StyleSheet</code>. * Setting the style name will have no impact on the local stylistic * properties of the <code>Component</code>. * * @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 <code>Component</code>. * * @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, new Boolean(oldValue), new Boolean(newValue)); } } /** * 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 <code>super.validate()</code> * out of convention. */ public void validate() { } /** * Invoked by the <code>ClientUpdateManager</code> on each component in the * hierarchy whose <code>processInput()</code> 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 <code>processInput()</code> implementation). * <p> * 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 * <code>super.verifyInput()</code>. * * @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 <code>Component</code> within the * <strong>visible</strong> children of this <code>Component</code>. If the * given <code>Component</code> is not a child, <code>-1</code> is * returned. * * @param c the <code>Component</code> to analyze * @return the index of the specified <code>Component</code> amongst the * <strong>visible</strong> children of this <code>Component</code> */ 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; } }