Index: test/unit/src/org/openide/explorer/propertysheet/PropertyPanelTest.java =================================================================== RCS file: test/unit/src/org/openide/explorer/propertysheet/PropertyPanelTest.java diff -N test/unit/src/org/openide/explorer/propertysheet/PropertyPanelTest.java *** /dev/null 1 Jan 1970 00:00:00 -0000 --- test/unit/src/org/openide/explorer/propertysheet/PropertyPanelTest.java 22 May 2002 11:48:24 -0000 *************** *** 0 **** --- 1,247 ---- + /* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun + * Microsystems, Inc. All Rights Reserved. + */ + + package org.openide.explorer.propertysheet; + + import java.beans.*; + import java.lang.reflect.*; + import javax.swing.*; + + import org.openide.*; + import org.openide.explorer.propertysheet.*; + + import junit.framework.*; + import junit.textui.TestRunner; + + import org.netbeans.junit.*; + import java.beans.PropertyDescriptor; + import java.awt.IllegalComponentStateException; + import java.lang.ref.WeakReference; + import java.lang.reflect.InvocationTargetException; + + /** A test of a property panel. + */ + public final class PropertyPanelTest extends NbTestCase { + + + public PropertyPanelTest(String name) { + super(name); + } + + public static void main (String[] args) { + junit.textui.TestRunner.run (new NbTestSuite (PropertyPanelTest.class)); + } + + // + // Sample property impl + // + + private String prop; + + public void setProp (String x) { + prop = x; + } + + public String getProp () { + return prop; + } + + protected void setUp() throws Exception { + } + + + public void testStateUpdates () throws Exception { + PropertyDescriptor feature = new PropertyDescriptor ("prop", this.getClass ()); + feature.setPropertyEditorClass (Ed.class); + DefaultPropertyModel model = new DefaultPropertyModel ( + this, feature + ); + + PropertyPanel pp = new PropertyPanel (model, pp.PREF_CUSTOM_EDITOR); + + assertTrue ("Ed editor created", pp.getPropertyEditor() instanceof Ed); + + Ed ed = (Ed)pp.getPropertyEditor (); + + assertNotNull("Environment has been attached", ed.env); + + Listener envListener = new Listener (); + Listener panelListener = new Listener (); + + pp.addPropertyChangeListener(panelListener); + ed.env.addPropertyChangeListener (envListener); + ed.env.addVetoableChangeListener (envListener); + + ed.env.setState (PropertyEnv.STATE_INVALID); + + assertEquals ("State of panel is invalid", PropertyEnv.STATE_INVALID, pp.getState ()); + envListener.assertChanges ("Notified in environment", 1, 1); + panelListener.assertChanges ("Notified in panel", 1, 0); + + ed.env.setState (PropertyEnv.STATE_INVALID); + assertEquals ("Remains invalid", PropertyEnv.STATE_INVALID, pp.getState ()); + envListener.assertChanges ("No changes notified", 0, 0); + panelListener.assertChanges ("No changes notified in panel", 0, 0); + + pp.updateValue(); + + assertEquals ("Update valud does not change the state if invalid", PropertyEnv.STATE_INVALID, pp.getState ()); + envListener.assertChanges ("Changes notified in env", 0, 0); + panelListener.assertChanges ("Notified in panel", 0, 0); + + ed.env.setState (PropertyEnv.STATE_NEEDS_VALIDATION); + assertEquals ("Now we need validation", PropertyEnv.STATE_NEEDS_VALIDATION, pp.getState ()); + envListener.assertChanges ("Notified in environment", 1, 1); + panelListener.assertChanges ("Notified in panel", 1, 0); + + pp.updateValue (); + assertEquals ("Update from needs validation shall switch to valid state if not vetoed", PropertyEnv.STATE_VALID, pp.getState ()); + envListener.assertChanges ("Notified in environment", 1, 1); + panelListener.assertChanges ("Notified in panel", 1, 0); + + ed.env.setState (PropertyEnv.STATE_NEEDS_VALIDATION); + assertEquals ("Now we need validation", PropertyEnv.STATE_NEEDS_VALIDATION, pp.getState ()); + envListener.assertChanges ("Notified in environment", 1, 1); + panelListener.assertChanges ("Notified in panel", 1, 0); + + + envListener.shallVeto = true; + pp.updateValue (); + assertTrue ("Was vetoed", !envListener.shallVeto); + + assertEquals ("The state remains", PropertyEnv.STATE_NEEDS_VALIDATION, pp.getState ()); + envListener.assertChanges ("No approved property changes", 0, -1); + panelListener.assertChanges ("No approved property changes", 0, -1); + + + // + // Now try to do the cleanup + // + + DefaultPropertyModel replace = new DefaultPropertyModel (this, "prop"); + pp.setModel (replace); + + assertEquals ("Model changed", replace, pp.getModel()); + + + WeakReference wEd = new WeakReference (ed); + WeakReference wEnv = new WeakReference (ed.env); + + ed = null; + + assertGC ("Property editor should disappear", wEd); + assertGC ("Environment should disapper", wEnv); + } + + public void testPropertyPanelShallGCEvenIfEditorExists () throws Exception { + PropertyDescriptor feature = new PropertyDescriptor ("prop", this.getClass ()); + feature.setPropertyEditorClass (Ed.class); + DefaultPropertyModel model = new DefaultPropertyModel ( + this, feature + ); + + PropertyPanel pp = new PropertyPanel (model, pp.PREF_CUSTOM_EDITOR); + + assertTrue ("Ed editor created", pp.getPropertyEditor() instanceof Ed); + + Ed ed = (Ed)pp.getPropertyEditor (); + assertNotNull("Environment has been attached", ed.env); + + // + // Make sure that the panel listens on changes in env + // + Listener panelListener = new Listener (); + + pp.addPropertyChangeListener(panelListener); + ed.env.setState (PropertyEnv.STATE_INVALID); + panelListener.assertChanges ("Change notified in panel", 1, 0); + + + WeakReference weak = new WeakReference (pp); + pp = null; + model = null; + feature = null; + + assertGC ("Panel should disappear even if we have reference to property editor", weak); + } + + /** Assert GC. + */ + private static void assertGC (String text, java.lang.ref.Reference ref) { + for (int i = 0; i < 10; i++) { + if (ref.get () == null) { + return; + } + System.gc (); + System.runFinalization(); + } + fail (text + " " + ref.get ()); + } + + /** Listener that counts changes. + */ + private static final class Listener + implements PropertyChangeListener, VetoableChangeListener { + public boolean shallVeto; + + private int veto; + private int change; + + public void assertChanges (String t, int c, int v) { + if (c != -1) { + assertEquals (t + " [propertychange]", c, change); + } + + if (v != -1) { + assertEquals (t + " [vetochange]", v, veto); + } + + change = 0; + veto = 0; + } + + public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent) { + change++; + } + + public void vetoableChange(java.beans.PropertyChangeEvent propertyChangeEvent) throws java.beans.PropertyVetoException { + if (shallVeto) { + shallVeto = false; + PropertyVetoException e = new PropertyVetoException ("Veto", propertyChangeEvent); + + // marks this exception as one that we do not want to notify + PropertyDialogManager.doNotNotify (e); + throw e; + } + + veto++; + } + + } + + /** Sample property editor. + */ + private static final class Ed extends java.beans.PropertyEditorSupport + implements ExPropertyEditor { + public PropertyEnv env; + + public Ed () { + } + + public void attachEnv(PropertyEnv env) { + this.env = env; + } + } + + } + Index: src/org/openide/explorer/propertysheet/PropertyDialogManager.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/PropertyDialogManager.java,v retrieving revision 1.58 diff -c -r1.58 PropertyDialogManager.java *** src/org/openide/explorer/propertysheet/PropertyDialogManager.java 17 May 2002 13:47:57 -0000 1.58 --- src/org/openide/explorer/propertysheet/PropertyDialogManager.java 22 May 2002 11:48:25 -0000 *************** *** 457,466 **** --- 457,479 ---- return NbBundle.getBundle(PropertyDialogManager.class).getString(key); } + /** For testing purposes we need to _not_ notify some exceptions. + * That is why here is a package private method to register an exception + * that should not be fired. + */ + static void doNotNotify (Throwable ex) { + doNotNotify = ex; + } + private static Throwable doNotNotify; + /** Notifies an exception to error manager or prints its it to stderr. * @param ex exception to notify */ static void notify(Throwable ex) { + Throwable d = doNotNotify; + doNotNotify = null; + if (d == ex) return; + ErrorManager.getDefault ().notify (ex); } *************** *** 468,473 **** --- 481,490 ---- * @param ex exception to notify */ static void notify(int severity, Throwable ex) { + Throwable d = doNotNotify; + doNotNotify = null; + if (d == ex) return; + ErrorManager.getDefault ().notify(severity, ex); } /** Index: src/org/openide/explorer/propertysheet/PropertyEnv.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/PropertyEnv.java,v retrieving revision 1.7 diff -c -r1.7 PropertyEnv.java *** src/org/openide/explorer/propertysheet/PropertyEnv.java 11 Apr 2002 16:31:38 -0000 1.7 --- src/org/openide/explorer/propertysheet/PropertyEnv.java 22 May 2002 11:48:26 -0000 *************** *** 14,19 **** --- 14,21 ---- package org.openide.explorer.propertysheet; import java.beans.FeatureDescriptor; + import java.beans.PropertyChangeListener; + import java.beans.PropertyChangeSupport; import java.beans.VetoableChangeListener; import java.beans.VetoableChangeSupport; import java.beans.PropertyVetoException; *************** *** 64,69 **** --- 66,73 ---- /** The support is lazy initialized in getSupport. */ private VetoableChangeSupport support; + /** change support here */ + private PropertyChangeSupport change; /** * The value of this field is basically taken from *************** *** 133,141 **** --- 137,148 ---- */ public void setState (Object state) { Object oldState = this.state; + // JST: should not this line move to //2//? this.state = state; try { getSupport().fireVetoableChange(PROP_STATE, oldState, state); + //2// this.state = state; + getChange ().firePropertyChange (PROP_STATE, oldState, state); } catch (PropertyVetoException pve) { // the change should already be reverted (refired) by // the fireVetoableChange method *************** *** 166,171 **** --- 173,186 ---- public void addVetoableChangeListener(VetoableChangeListener l) { getSupport().addVetoableChangeListener(l); } + + /** + * Property change listener: listenning here you will be notified + * when the state of the environment is has been changed. + */ + public void addPropertyChangeListener (PropertyChangeListener l) { + getChange ().addPropertyChangeListener (l); + } /** * Vetoable change listener removal. *************** *** 174,179 **** --- 189,202 ---- getSupport().removeVetoableChangeListener(l); } + /** + * Removes Property change listener. + */ + public void removePropertyChangeListener (PropertyChangeListener l) { + getChange ().removePropertyChangeListener (l); + } + + /** Getter for property changeImmediate. * @return Value of property changeImmediate. */ *************** *** 197,201 **** --- 220,235 ---- } return support; } + + /** + * Lazy initialization of the PropertyChangeSupport. + */ + private synchronized PropertyChangeSupport getChange () { + if (change == null) { + change = new PropertyChangeSupport (this); + } + return change; + } + } Index: src/org/openide/explorer/propertysheet/PropertyPanel.java =================================================================== RCS file: /cvs/openide/src/org/openide/explorer/propertysheet/PropertyPanel.java,v retrieving revision 1.107 diff -c -r1.107 PropertyPanel.java *** src/org/openide/explorer/propertysheet/PropertyPanel.java 18 Apr 2002 17:09:21 -0000 1.107 --- src/org/openide/explorer/propertysheet/PropertyPanel.java 22 May 2002 11:48:28 -0000 *************** *** 92,97 **** --- 92,100 ---- /** Name of the read-only property 'propertyEditor'. */ public static final String PROP_PROPERTY_EDITOR = "propertyEditor"; // NOI18N + /** Name of property 'state' that describes the state of the embeded PropertyEditor */ + public static final String PROP_STATE = PropertyEnv.PROP_STATE; + /** Name of 'canEditAsText' property. */ private static final String PROP_CAN_EDIT_AS_TEXT = "canEditAsText"; // NOI18N *************** *** 350,364 **** reset(); firePropertyChange (PROP_MODEL, oldModel, model); } ! ! /** ! * Updates the value if * org.openide.explorer.propertysheet.editors.EnhancedCustomPropertyEditor ! * is used. */ public void updateValue() { if (editor == null) return; if (editor.supportsCustomEditor()) { Component customEditor = editor.getCustomEditor(); if (customEditor instanceof EnhancedCustomPropertyEditor) { --- 353,390 ---- reset(); firePropertyChange (PROP_MODEL, oldModel, model); } ! ! /** Getter for the state of the property editor. The editor can be in ! * not valid states just if it implements the ExPropertyEditor ! * and changes state by the setState method of the PropertyEnv ! * environment. ! *

! * @return PropertyEnv.STATE_VALID if the editor is not the ExPropertyEditor ! * one or other constant from PropertyEnv.STATE_* that was assigned to PropertyEnv ! */ ! public final Object getState () { ! PropertyEnv e = env; ! return e == null ? PropertyEnv.STATE_VALID : e.getState(); ! } ! ! /** If the editor is ExPropertyEditor it tries to change the ! * getState property to PropertyEnv.STATE_VALID ! * state. This may be vetoed, in such case a warning is presented to the user ! * and the getState will still return the original value ! * (different from STATE_VALID). ! *

! * Also updates the value if * org.openide.explorer.propertysheet.editors.EnhancedCustomPropertyEditor ! * is used. */ public void updateValue() { if (editor == null) return; + PropertyEnv e = env; + if (e != null && e.getState () == PropertyEnv.STATE_NEEDS_VALIDATION) { + e.setState (PropertyEnv.STATE_VALID); + } + if (editor.supportsCustomEditor()) { Component customEditor = editor.getCustomEditor(); if (customEditor instanceof EnhancedCustomPropertyEditor) { *************** *** 588,593 **** --- 614,623 ---- // find new editor editor = null; + if (env != null) { + env.removePropertyChangeListener(getEditorListener ()); + } + env = null; if (model instanceof ExPropertyModel) { *************** *** 611,617 **** Class editorClass = model.getPropertyEditorClass(); if (editorClass != null) { try { ! editor = (PropertyEditor) editorClass.newInstance(); } catch (Exception e) { PropertyDialogManager.notify(e); } --- 641,649 ---- Class editorClass = model.getPropertyEditorClass(); if (editorClass != null) { try { ! java.lang.reflect.Constructor c = editorClass.getConstructor(new Class[0]); ! c.setAccessible (true); ! editor = (PropertyEditor) c.newInstance(new Object[0]); } catch (Exception e) { PropertyDialogManager.notify(e); } *************** *** 659,664 **** --- 691,699 ---- } if (canWrite) { editor.addPropertyChangeListener(getEditorListener()); + if (env != null) { + env.addPropertyChangeListener (getEditorListener ()); + } } } *************** *** 1865,1870 **** --- 1900,1916 ---- private class EditorListener implements PropertyChangeListener { /** Property was changed. */ public void propertyChange(PropertyChangeEvent evt) { + if (evt.getSource () instanceof PropertyEnv) { + // we are listening also on PropertyEnv + + if (PropertyEnv.PROP_STATE.equals (evt.getPropertyName ())) { + PropertyPanel.this.firePropertyChange (PropertyEnv.PROP_STATE, evt.getOldValue(), evt.getNewValue()); + } + + return; + } + + if (ignoreEvents) return; if ((!isWriteState) && (!customDialogShown) &&