org.metawidget.swing.SwingMetawidget Maven / Gradle / Ivy
// Metawidget (licensed under LGPL)
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package org.metawidget.swing;
import static org.metawidget.inspector.InspectionResultConstants.*;
import java.awt.BasicStroke;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.beans.Beans;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import org.metawidget.iface.Immutable;
import org.metawidget.iface.MetawidgetException;
import org.metawidget.inspectionresultprocessor.iface.InspectionResultProcessor;
import org.metawidget.inspector.iface.Inspector;
import org.metawidget.layout.iface.Layout;
import org.metawidget.pipeline.w3c.W3CPipeline;
import org.metawidget.util.ArrayUtils;
import org.metawidget.util.ClassUtils;
import org.metawidget.util.CollectionUtils;
import org.metawidget.util.simple.PathUtils;
import org.metawidget.util.simple.PathUtils.TypeAndNames;
import org.metawidget.util.simple.StringUtils;
import org.metawidget.widgetbuilder.composite.CompositeWidgetBuilder;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;
import org.metawidget.widgetprocessor.iface.WidgetProcessor;
import org.w3c.dom.Element;
/**
* Metawidget for Swing environments.
*
* @author Richard Kennard
*/
// Note: It would be nice for SwingMetawidget to extend AwtMetawidget, but we want
// SwingMetawidget to extend JComponent for various Swing-specific features like setBorder and
// setOpaque
//
public class SwingMetawidget
extends JComponent {
//
// Private statics
//
private static final Stroke STROKE_DOTTED = new BasicStroke( 1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0f, new float[] { 3f }, 0f );
//
// Private members
//
private Object mToInspect;
private String mPath;
private ResourceBundle mBundle;
private boolean mNeedToBuildWidgets;
private Element mLastInspectionResult;
private boolean mIgnoreAddRemove;
/**
* List of existing, manually added components.
*
* This is a List, not a Set, so that mExistingUnusedComponents (which is initialized from it)
* is consistent.
*/
private List mExistingComponents = CollectionUtils.newArrayList();
/**
* List of existing, manually added, but unused by Metawidget components.
*
* This is a List, not a Set, for consistency during endBuild.
*/
private List mExistingUnusedComponents;
private Map mFacets = CollectionUtils.newHashMap();
/* package private */Pipeline mPipeline;
//
// Constructor
//
public SwingMetawidget() {
mPipeline = newPipeline();
}
//
// Public methods
//
/**
* Sets the Object to inspect.
*
* If setPath
has not been set, or points to a previous setToInspect
,
* sets it to point to the given Object.
*/
public void setToInspect( Object toInspect ) {
updateToInspectWithoutInvalidate( toInspect );
invalidateInspection();
}
/**
* Updates the Object to inspect, without invalidating the previous inspection results.
*
* This is an internal API exposed for WidgetProcessor rebinding support. Clients should
* not call it directly.
*/
public void updateToInspectWithoutInvalidate( Object toInspect ) {
if ( mToInspect == null ) {
if ( mPath == null && toInspect != null ) {
mPath = toInspect.getClass().getName();
}
} else if ( mToInspect.getClass().getName().equals( mPath ) ) {
if ( toInspect == null ) {
mPath = null;
} else {
mPath = toInspect.getClass().getName();
}
}
mToInspect = toInspect;
}
/**
* Gets the Object being inspected.
*
* Exposed for binding implementations.
*
* @return the object. Note this return type uses generics, so as to not require a cast by the
* caller (eg. Person p = getToInspect()
)
*/
@SuppressWarnings( "unchecked" )
public T getToInspect() {
return (T) mToInspect;
}
/**
* Sets the path to be inspected.
*
* Note setPath
is quite different to java.awt.Component.setName
.
* setPath
is always in relation to setToInspect
, so must include the
* type name and any subsequent sub-names (eg. type/name/name). Conversely, setName
* is a single name relative to our immediate parent.
*/
public void setPath( String path ) {
mPath = path;
invalidateInspection();
}
public String getPath() {
return mPath;
}
public void setConfig( String config ) {
mPipeline.setConfig( config );
invalidateInspection();
}
public void setInspector( Inspector inspector ) {
mPipeline.setInspector( inspector );
invalidateInspection();
}
/**
* Useful for WidgetBuilders to perform nested inspections (eg. for Collections).
*/
public String inspect( Object toInspect, String type, String... names ) {
return mPipeline.inspect( toInspect, type, names );
}
public void addInspectionResultProcessor( InspectionResultProcessor inspectionResultProcessor ) {
mPipeline.addInspectionResultProcessor( inspectionResultProcessor );
invalidateInspection();
}
public void removeInspectionResultProcessor( InspectionResultProcessor inspectionResultProcessor ) {
mPipeline.removeInspectionResultProcessor( inspectionResultProcessor );
invalidateInspection();
}
public void setInspectionResultProcessors( InspectionResultProcessor... inspectionResultProcessors ) {
mPipeline.setInspectionResultProcessors( inspectionResultProcessors );
invalidateInspection();
}
public void setWidgetBuilder( WidgetBuilder widgetBuilder ) {
mPipeline.setWidgetBuilder( widgetBuilder );
invalidateWidgets();
}
public WidgetBuilder getWidgetBuilder() {
buildWidgets();
return mPipeline.getWidgetBuilder();
}
public void addWidgetProcessor( WidgetProcessor widgetProcessor ) {
mPipeline.addWidgetProcessor( widgetProcessor );
invalidateWidgets();
}
public void removeWidgetProcessor( WidgetProcessor widgetProcessor ) {
mPipeline.removeWidgetProcessor( widgetProcessor );
invalidateWidgets();
}
public void setWidgetProcessors( WidgetProcessor... widgetProcessors ) {
mPipeline.setWidgetProcessors( widgetProcessors );
invalidateWidgets();
}
public T getWidgetProcessor( Class widgetProcessorClass ) {
buildWidgets();
return mPipeline.getWidgetProcessor( widgetProcessorClass );
}
/**
* Set the layout for this Metawidget.
*
* Named setMetawidgetLayout
, rather than the usual setLayout
, because
* Swing already defines a setLayout
. Overloading Swing's setLayout
* was considered cute, but ultimately confusing and dangerous. For example, what should
* setLayout( null )
do?
*/
public void setMetawidgetLayout( Layout layout ) {
mPipeline.setLayout( layout );
invalidateWidgets();
}
public Layout getMetawidgetLayout() {
buildWidgets();
return mPipeline.getLayout();
}
public void setBundle( ResourceBundle bundle ) {
mBundle = bundle;
invalidateWidgets();
}
public String getLabelString( Map attributes ) {
if ( attributes == null ) {
return "";
}
// Explicit label
String label = attributes.get( LABEL );
if ( label != null ) {
// (may be forced blank)
if ( "".equals( label ) ) {
return null;
}
// (localize if possible)
String localized = getLocalizedKey( StringUtils.camelCase( label ) );
if ( localized != null ) {
return localized.trim();
}
return label.trim();
}
// Default name
String name = attributes.get( NAME );
if ( name != null ) {
// (localize if possible)
String localized = getLocalizedKey( name );
if ( localized != null ) {
return localized.trim();
}
return StringUtils.uncamelCase( name );
}
return "";
}
/**
* @return null if no bundle, ???key??? if bundle is missing a key
*/
public String getLocalizedKey( String key ) {
if ( mBundle == null ) {
return null;
}
try {
return mBundle.getString( key );
} catch ( MissingResourceException e ) {
return StringUtils.RESOURCE_KEY_NOT_FOUND_PREFIX + key + StringUtils.RESOURCE_KEY_NOT_FOUND_SUFFIX;
}
}
public boolean isReadOnly() {
return mPipeline.isReadOnly();
}
public void setReadOnly( boolean readOnly ) {
if ( mPipeline.isReadOnly() == readOnly ) {
return;
}
mPipeline.setReadOnly( readOnly );
invalidateWidgets();
}
public int getMaximumInspectionDepth() {
return mPipeline.getMaximumInspectionDepth();
}
public void setMaximumInspectionDepth( int maximumInspectionDepth ) {
mPipeline.setMaximumInspectionDepth( maximumInspectionDepth );
invalidateWidgets();
}
/**
* Fetch a list of JComponents
that were added manually, and have so far not been
* used.
*
* This is an internal API exposed for OverriddenWidgetBuilder. Clients should not call
* it directly.
*/
public List fetchExistingUnusedComponents() {
return mExistingUnusedComponents;
}
//
// The following methods all kick off buildWidgets() if necessary
//
/**
* Overridden to build widgets just-in-time.
*
* This is the first method a JFrame.pack calls.
*/
@Override
public Dimension getPreferredSize() {
buildWidgets();
return super.getPreferredSize();
}
/**
* Overridden to build widgets just-in-time.
*
* This is the first method a JTable.editCellAt calls.
*/
@Override
public void setBounds( Rectangle rectangle ) {
buildWidgets();
super.setBounds( rectangle );
}
/**
* Overridden to build widgets just-in-time.
*
* This is the first method a JComponent.paintChildren calls (this includes JDialog)
*/
@Override
public Rectangle getBounds( Rectangle rectangle ) {
buildWidgets();
return super.getBounds( rectangle );
}
/**
* Overridden to build widgets just-in-time.
*
* This method may be called by developers who wish to modify the created Components before they
* are displayed. For example, they may wish to call .setBorder( null ) if the component is to
* be used as a JTable CellEditor.
*/
@Override
public Component getComponent( int index ) {
buildWidgets();
return super.getComponent( index );
}
/**
* Overridden to build widgets just-in-time.
*
* This method may be called by developers who wish to modify the created Components before they
* are displayed. For example, they may wish to call .setBorder( null ) if the component is to
* be used as a JTable CellEditor.
*/
@Override
public Component[] getComponents() {
buildWidgets();
return super.getComponents();
}
/**
* Overridden to build widgets just-in-time.
*
* This method may be called by developers who wish to modify the created Components before they
* are displayed.
*/
@Override
public int getComponentCount() {
buildWidgets();
return super.getComponentCount();
}
/**
* Overridden to build widgets just-in-time.
*
* This method may be called by developers who wish to test the SwingMetawidget's active
* LayoutManager.
*/
@Override
public LayoutManager getLayout() {
buildWidgets();
return super.getLayout();
}
/**
* Overridden to build widgets just-in-time.
*
* When adding a Stub that immediately stubs out a widget and therefore disappears from the
* component list, we must override addNotify
to build widgets or else Swing gets
* confused trying to addNotify a component that isn't there.
*
* See SwingTutorialTest.testAddNotify
.
*/
@Override
public void addNotify() {
buildWidgets();
super.addNotify();
}
/**
* Gets the value from the Component with the given name.
*
* The value is returned as it was stored in the Component (eg. String for JTextField) so may
* need some conversion before being reapplied to the object being inspected. This obviously
* requires knowledge of which Component SwingMetawidget created, which is not ideal, so clients
* may prefer to use a binding WidgetProcessor instead.
*
* @return the value. Note this return type uses generics, so as to not require a cast by the
* caller (eg. String s = getValue(names)
)
*/
@SuppressWarnings( "unchecked" )
public T getValue( String... names ) {
ComponentAndValueProperty componentAndValueProperty = getComponentAndValueProperty( names );
return (T) ClassUtils.getProperty( componentAndValueProperty.getComponent(), componentAndValueProperty.getValueProperty() );
}
/**
* Sets the Component with the given name to the specified value.
*
* Clients must ensure the value is of the correct type to suit the Component (eg. String for
* JTextField). This obviously requires knowledge of which Component SwingMetawidget created,
* which is not ideal, so clients may prefer to use a binding WidgetProcessor instead.
*/
public void setValue( Object value, String... names ) {
ComponentAndValueProperty componentAndValueProperty = getComponentAndValueProperty( names );
ClassUtils.setProperty( componentAndValueProperty.getComponent(), componentAndValueProperty.getValueProperty(), value );
}
/**
* Returns the property used to get/set the value of the component.
*
* If the component is not known, returns null
. Does not throw an Exception, as we
* want to fail gracefully if, say, someone tries to bind to a JPanel.
*/
public String getValueProperty( Component component ) {
return getValueProperty( component, mPipeline.getWidgetBuilder() );
}
/**
* Finds the Component with the given name.
*/
@SuppressWarnings( "unchecked" )
public T getComponent( String... names ) {
if ( names == null || names.length == 0 ) {
return null;
}
Component topComponent = this;
for ( int loop = 0, length = names.length; loop < length; loop++ ) {
String name = names[loop];
// May need building 'just in time' if we are calling getComponent
// immediately after a 'setToInspect'. See
// SwingMetawidgetTest.testNestedWithManualInspector
if ( topComponent instanceof SwingMetawidget ) {
( (SwingMetawidget) topComponent ).buildWidgets();
}
// Try to find a component
topComponent = getComponent( (Container) topComponent, name );
if ( loop == length - 1 ) {
return (T) topComponent;
}
if ( topComponent == null ) {
throw MetawidgetException.newException( "No such component '" + name + "' of '" + ArrayUtils.toString( names, "', '" ) + "'" );
}
}
return (T) topComponent;
}
public Facet getFacet( String name ) {
buildWidgets();
return mFacets.get( name );
}
@Override
public void remove( Component component ) {
super.remove( component );
if ( !mIgnoreAddRemove ) {
invalidateWidgets();
if ( component instanceof Facet ) {
mFacets.remove( ( (Facet) component ).getName() );
} else {
mExistingComponents.remove( component );
}
}
}
@Override
public void remove( int index ) {
Component component = getComponent( index );
// (don't be tempted to call remove( component ), as that may infinite
// recurse on some JDK implementations)
super.remove( index );
if ( !mIgnoreAddRemove ) {
invalidateWidgets();
if ( component instanceof Facet ) {
mFacets.remove( ( (Facet) component ).getName() );
} else {
mExistingComponents.remove( component );
}
}
}
@Override
public void removeAll() {
super.removeAll();
if ( !mIgnoreAddRemove ) {
invalidateWidgets();
mFacets.clear();
mExistingComponents.clear();
}
}
/**
* Useful for WidgetBuilders to setup nested Metawidgets (eg. for initializing a dummy
* Metawidget to rebind child Metawidgets in a Collection).
*/
public void initNestedMetawidget( SwingMetawidget nestedMetawidget, Map attributes ) {
// Don't copy setConfig(). Instead, copy runtime values
mPipeline.initNestedPipeline( nestedMetawidget.mPipeline, attributes );
nestedMetawidget.setPath( mPath + StringUtils.SEPARATOR_FORWARD_SLASH_CHAR + attributes.get( NAME ) );
nestedMetawidget.setBundle( mBundle );
nestedMetawidget.setOpaque( isOpaque() );
nestedMetawidget.setToInspect( mToInspect );
}
//
// Protected methods
//
/**
* Instantiate the Pipeline used by this Metawidget.
*
* Subclasses wishing to use their own Pipeline should override this method to instantiate their
* version.
*/
protected Pipeline newPipeline() {
return new Pipeline();
}
protected String getDefaultConfiguration() {
return ClassUtils.getPackagesAsFolderNames( SwingMetawidget.class ) + "/metawidget-swing-default.xml";
}
@Override
protected void paintComponent( Graphics graphics ) {
buildWidgets();
super.paintComponent( graphics );
// When used as part of an IDE builder tool, render as a dotted square so that we can see
// something!
if ( Beans.isDesignTime() ) {
Graphics2D graphics2d = (Graphics2D) graphics;
Stroke strokeBefore = graphics2d.getStroke();
try {
graphics2d.setStroke( STROKE_DOTTED );
int height = getHeight();
graphics2d.drawRect( 0, 0, getWidth() - 1, height - 1 );
graphics2d.drawString( "Metawidget", 10, height / 2 );
} finally {
graphics2d.setStroke( strokeBefore );
}
}
}
/**
* Invalidates the current inspection result (if any) and invalidates the widgets.
*
* As an optimisation we only invalidate the widgets, not the entire inspection result, for some
* operations (such as adding/removing stubs, changing read-only etc.)
*/
protected void invalidateInspection() {
mLastInspectionResult = null;
invalidateWidgets();
}
/**
* Invalidates the widgets.
*/
protected void invalidateWidgets() {
if ( mNeedToBuildWidgets ) {
return;
}
// Note: it is important to call removeAll BEFORE setting mNeedToBuildWidgets
// to true. On some JRE implementations (ie. 1.6_12) removeAll triggers an
// immediate repaint which sets mNeedToBuildWidgets back to false
super.removeAll();
// Prepare to build widgets
mNeedToBuildWidgets = true;
// Call repaint here, rather than just 'invalidate', for scenarios like doing
// a 'remove' of a button that masks a Metawidget
repaint();
}
@Override
protected void addImpl( Component component, Object constraints, int index ) {
if ( !mIgnoreAddRemove ) {
invalidateWidgets();
// Don't fall through to super.addImpl for facets. Tuck them away
// in mFacets instead. Some layouts may never use them, and
// others (eg. MigLayout) don't like adding components
// without constraints
if ( component instanceof Facet ) {
mFacets.put( component.getName(), (Facet) component );
return;
}
if ( component instanceof JComponent ) {
mExistingComponents.add( (JComponent) component );
}
}
super.addImpl( component, constraints, index );
}
protected void buildWidgets() {
// No need to build?
if ( !mNeedToBuildWidgets || Beans.isDesignTime() ) {
return;
}
mPipeline.configureOnce();
mNeedToBuildWidgets = false;
mIgnoreAddRemove = true;
try {
if ( mLastInspectionResult == null ) {
mLastInspectionResult = inspect();
}
mPipeline.buildWidgets( mLastInspectionResult );
} catch ( Exception e ) {
throw MetawidgetException.newException( e );
} finally {
mIgnoreAddRemove = false;
}
}
protected void startBuild() {
mExistingUnusedComponents = CollectionUtils.newArrayList( mExistingComponents );
}
/**
* @param elementName
* XML node name of the business field. Typically 'entity', 'property' or 'action'.
* Never null
*/
protected void layoutWidget( Component component, String elementName, Map attributes ) {
// Set the name of the component.
//
// If this is a JScrollPane, set the name of the top-level JScrollPane. Don't do this before
// now, as we don't want binding/validation implementations accidentally relying on the
// name being set (which it won't be for actualComponent)
//
// Note: we haven't split this out into a separate WidgetProcessor, because other methods
// like getValue/setValue/getComponent( String... names ) rely on it
component.setName( attributes.get( NAME ) );
// Remove, then re-add to layout (to re-order the component)
remove( component );
// Look up any additional attributes
Map additionalAttributes = mPipeline.getAdditionalAttributes( (JComponent) component );
if ( additionalAttributes != null ) {
attributes.putAll( additionalAttributes );
}
// BasePipeline will call .layoutWidget
}
protected void endBuild() {
if ( mExistingUnusedComponents != null ) {
for ( JComponent componentExisting : mExistingUnusedComponents ) {
// Unused facets don't count
if ( componentExisting instanceof Facet ) {
continue;
}
// Manually created components default to no section
Map attributes = CollectionUtils.newHashMap();
attributes.put( SECTION, "" );
mPipeline.layoutWidget( componentExisting, PROPERTY, attributes );
}
}
}
//
// Private methods
//
private Element inspect() {
if ( mPath == null ) {
return null;
}
TypeAndNames typeAndNames = PathUtils.parsePath( mPath );
return mPipeline.inspectAsDom( mToInspect, typeAndNames.getType(), typeAndNames.getNamesAsArray() );
}
private ComponentAndValueProperty getComponentAndValueProperty( String... names ) {
Component component = getComponent( names );
if ( component == null ) {
throw MetawidgetException.newException( "No component named '" + ArrayUtils.toString( names, "', '" ) + "'" );
}
// Drill into JScrollPanes
if ( component instanceof JScrollPane ) {
component = ( (JScrollPane) component ).getViewport().getView();
}
String componentProperty = getValueProperty( component );
if ( componentProperty == null ) {
throw MetawidgetException.newException( "Don't know how to getValue from a " + component.getClass().getName() );
}
return new ComponentAndValueProperty( component, componentProperty );
}
private String getValueProperty( Component component, WidgetBuilder widgetBuilder ) {
// Recurse into CompositeWidgetBuilders
try {
if ( widgetBuilder instanceof CompositeWidgetBuilder, ?> ) {
for ( WidgetBuilder widgetBuilderChild : ( (CompositeWidgetBuilder) widgetBuilder ).getWidgetBuilders() ) {
String valueProperty = getValueProperty( component, widgetBuilderChild );
if ( valueProperty != null ) {
return valueProperty;
}
}
return null;
}
} catch ( NoClassDefFoundError e ) {
// May not be shipping with CompositeWidgetBuilder
}
// Interrogate ValuePropertyProviders
if ( widgetBuilder instanceof SwingValuePropertyProvider ) {
return ( (SwingValuePropertyProvider) widgetBuilder ).getValueProperty( component );
}
return null;
}
private Component getComponent( Container container, String name ) {
for ( Component childComponent : container.getComponents() ) {
// Drill into unnamed containers (ie. for TabbedPanes)
if ( childComponent.getName() == null && childComponent instanceof Container ) {
childComponent = getComponent( (Container) childComponent, name );
if ( childComponent != null ) {
return childComponent;
}
continue;
}
// Match by name
if ( name.equals( childComponent.getName() ) ) {
return childComponent;
}
}
// Not found
return null;
}
//
// Inner class
//
protected class Pipeline
extends W3CPipeline {
//
// Protected methods
//
@Override
protected SwingMetawidget getPipelineOwner() {
return SwingMetawidget.this;
}
@Override
protected String getDefaultConfiguration() {
return SwingMetawidget.this.getDefaultConfiguration();
}
@Override
protected void configure() {
// Special support for visual IDE builders
if ( Beans.isDesignTime() ) {
return;
}
super.configure();
}
@Override
protected void configureDefaults() {
super.configureDefaults();
// SwingMetawidget uses setMetawidgetLayout, not setLayout
if ( getLayout() == null ) {
getConfigReader().configure( getDefaultConfiguration(), getPipelineOwner(), "metawidgetLayout" );
}
}
@Override
protected void startBuild() {
super.startBuild();
SwingMetawidget.this.startBuild();
}
@Override
protected void layoutWidget( JComponent component, String elementName, Map attributes ) {
SwingMetawidget.this.layoutWidget( component, elementName, attributes );
// Support null layouts
if ( getLayout() == null ) {
add( component );
} else {
super.layoutWidget( component, elementName, attributes );
}
// If the component is itself a SwingMetawidget, build it immediately. This:
//
// 1. stops an addNotify problem if OverriddenWidgetBuilder steals a component from its
// parent
// 2. stops us having to check for (name == null) before calling component.setName in
// layoutWidget (because the component will already have been stolen if needed)
// 3. means the endBuild of the parent SwingMetawidget gets called *after* the endBuild
// of the nested SwingMetawidget. This makes more sense, as otherwise the nested
// SwingMetawidget waits until being asked to paint, which is after the endBuild of the
// parent
if ( component instanceof SwingMetawidget ) {
( (SwingMetawidget) component ).buildWidgets();
}
}
@Override
protected Map getAdditionalAttributes( JComponent component ) {
if ( component instanceof Stub ) {
return ( (Stub) component ).getAttributes();
}
return null;
}
@Override
public SwingMetawidget buildNestedMetawidget( Map attributes )
throws Exception {
SwingMetawidget nestedMetawidget = SwingMetawidget.this.getClass().newInstance();
SwingMetawidget.this.initNestedMetawidget( nestedMetawidget, attributes );
return nestedMetawidget;
}
@Override
protected void endBuild() {
SwingMetawidget.this.endBuild();
super.endBuild();
// Call validate because Components have been added/removed, and
// Component layout information has changed. Must do this after
// super.endBuild, because that is what calls layout.endBuild
validate();
}
}
/**
* Simple immutable structure to store a component and its value property.
*
* @author Richard Kennard
*/
private static class ComponentAndValueProperty
implements Immutable {
//
// Private members
//
private Component mComponent;
private String mValueProperty;
//
// Constructor
//
public ComponentAndValueProperty( Component component, String valueProperty ) {
mComponent = component;
mValueProperty = valueProperty;
}
//
// Public methods
//
public Component getComponent() {
return mComponent;
}
public String getValueProperty() {
return mValueProperty;
}
}
}