001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.openide.explorer.propertysheet;
043:
044: import java.awt.Component;
045: import java.awt.FlowLayout;
046: import java.beans.*;
047: import java.lang.reflect.*;
048: import javax.swing.*;
049:
050: import org.openide.*;
051: import org.openide.explorer.propertysheet.*;
052:
053: import junit.framework.*;
054: import junit.textui.TestRunner;
055:
056: import org.netbeans.junit.*;
057: import java.beans.PropertyDescriptor;
058: import java.awt.IllegalComponentStateException;
059: import java.lang.ref.WeakReference;
060: import java.lang.reflect.InvocationTargetException;
061: import javax.swing.JPanel;
062:
063: /** A test of a property panel.
064: */
065: public final class PropertyPanelTest extends NbTestCase {
066: static {
067: //Added with property panel rewrite - the property model given the
068: //variable name "replace" is a String property, and the default
069: //property editor for the JDK does not support a custom editor.
070: //PropertyPanel will no longer allow itself to be put in custom editor
071: //mode if the property does not support a custom editor.
072: String[] syspesp = PropertyEditorManager.getEditorSearchPath();
073: String[] nbpesp = new String[] {
074: "org.netbeans.beaninfo.editors", // NOI18N
075: "org.openide.explorer.propertysheet.editors", // NOI18N
076: };
077: String[] allpesp = new String[syspesp.length + nbpesp.length];
078: System.arraycopy(nbpesp, 0, allpesp, 0, nbpesp.length);
079: System.arraycopy(syspesp, 0, allpesp, nbpesp.length,
080: syspesp.length);
081: PropertyEditorManager.setEditorSearchPath(allpesp);
082: }
083:
084: public PropertyPanelTest(String name) {
085: super (name);
086: }
087:
088: public static void main(String[] args) {
089: junit.textui.TestRunner.run(new NbTestSuite(
090: PropertyPanelTest.class));
091: }
092:
093: //
094: // Sample property impl
095: //
096:
097: private String prop;
098:
099: public void setProp(String x) {
100: prop = x;
101: }
102:
103: public String getProp() {
104: return prop;
105: }
106:
107: JFrame jf;
108:
109: protected void setUp() throws Exception {
110: PropUtils.forceRadioButtons = false;
111: jf = new JFrame();
112: jf.getContentPane().setLayout(new FlowLayout());
113: jf.setSize(400, 400);
114: jf.setLocation(30, 30);
115: jf.show();
116: }
117:
118: public void testStateUpdates() throws Exception {
119: PropertyDescriptor feature = new PropertyDescriptor("prop",
120: this .getClass());
121: feature.setPropertyEditorClass(Ed.class);
122: DefaultPropertyModel model = new DefaultPropertyModel(this ,
123: feature);
124:
125: final PropertyPanel pp = new PropertyPanel(model,
126: PropertyPanel.PREF_CUSTOM_EDITOR);
127:
128: //The property panel must be displayed - it will not attempt to communicate
129: //with the property editor until it is on screen
130: SwingUtilities.invokeAndWait(new Runnable() {
131: public void run() {
132: jf.getContentPane().add(pp);
133: System.err.println(" Aded to panel");
134: jf.validate();
135: jf.repaint();
136: System.err.println(" bounds: " + pp.getBounds());
137: }
138: });
139: Thread.currentThread().sleep(1000);
140:
141: assertTrue("Ed editor created",
142: pp.getPropertyEditor() instanceof Ed);
143:
144: Ed ed = (Ed) pp.getPropertyEditor();
145:
146: assertNotNull(
147: "PropertyPanel returns the right property editor", ed);
148:
149: assertNotNull("Environment has not been attached", ed.env);
150:
151: Listener envListener = new Listener();
152: Listener panelListener = new Listener();
153:
154: pp.addPropertyChangeListener(panelListener);
155: ed.env.addPropertyChangeListener(envListener);
156: ed.env.addVetoableChangeListener(envListener);
157:
158: ed.env.setState(PropertyEnv.STATE_INVALID);
159:
160: assertEquals("State of panel is invalid",
161: PropertyEnv.STATE_INVALID, pp.getState());
162: envListener.assertChanges("Notified in environment", 1, 1);
163: panelListener.assertChanges("Notified in panel", 1, 0);
164:
165: ed.env.setState(PropertyEnv.STATE_INVALID);
166: assertEquals("Remains invalid", PropertyEnv.STATE_INVALID, pp
167: .getState());
168: envListener.assertChanges("No changes notified", 0, 0);
169: panelListener.assertChanges("No changes notified in panel", 0,
170: 0);
171:
172: pp.updateValue();
173:
174: assertEquals(
175: "Update valud does not change the state if invalid",
176: PropertyEnv.STATE_INVALID, pp.getState());
177: envListener.assertChanges("Changes notified in env", 0, 0);
178: panelListener.assertChanges("Notified in panel", 0, 0);
179:
180: ed.env.setState(PropertyEnv.STATE_NEEDS_VALIDATION);
181: assertEquals("Now we need validation",
182: PropertyEnv.STATE_NEEDS_VALIDATION, pp.getState());
183: envListener.assertChanges("Notified in environment", 1, 1);
184: panelListener.assertChanges("Notified in panel", 1, 0);
185:
186: pp.updateValue();
187: assertEquals(
188: "Update from needs validation shall switch to valid state if not vetoed",
189: PropertyEnv.STATE_VALID, pp.getState());
190: envListener.assertChanges("Notified in environment", 1, 1);
191: panelListener.assertChanges("Notified in panel", 1, 0);
192:
193: ed.env.setState(PropertyEnv.STATE_NEEDS_VALIDATION);
194: assertEquals("Now we need validation",
195: PropertyEnv.STATE_NEEDS_VALIDATION, pp.getState());
196: envListener.assertChanges("Notified in environment", 1, 1);
197: panelListener.assertChanges("Notified in panel", 1, 0);
198:
199: envListener.shallVeto = true;
200: pp.updateValue();
201: assertTrue("Was vetoed", !envListener.shallVeto);
202:
203: assertEquals("The state remains",
204: PropertyEnv.STATE_NEEDS_VALIDATION, pp.getState());
205: envListener
206: .assertChanges("No approved property changes", 0, -1);
207: panelListener.assertChanges("No approved property changes", 0,
208: -1);
209:
210: //
211: // Now try to do the cleanup
212: //
213:
214: DefaultPropertyModel replace = new DefaultPropertyModel(this ,
215: "prop");
216: pp.setModel(replace);
217:
218: assertEquals("Model changed", replace, pp.getModel());
219:
220: WeakReference wEd = new WeakReference(ed);
221: WeakReference wEnv = new WeakReference(ed.env);
222:
223: ed = null;
224:
225: assertGC("Property editor should disappear", wEd);
226: assertGC("Environment should disapper", wEnv);
227: }
228:
229: private void addToPanel(final PropertyPanel pp) throws Exception {
230: //The property panel must be displayed - it will not attempt to communicate
231: //with the property editor until it is on screen
232: SwingUtilities.invokeAndWait(new Runnable() {
233: public void run() {
234: jf.getContentPane().add(pp);
235: jf.validate();
236: jf.repaint();
237: }
238: });
239: Thread.currentThread().sleep(500);
240: }
241:
242: private void removeFromPanel(final PropertyPanel pp)
243: throws Exception {
244: //The property panel must be displayed - it will not attempt to communicate
245: //with the property editor until it is on screen
246: SwingUtilities.invokeAndWait(new Runnable() {
247: public void run() {
248: jf.getContentPane().remove(pp);
249: jf.validate();
250: jf.repaint();
251: }
252: });
253: Thread.currentThread().sleep(500);
254: }
255:
256: public void testPropertyPanelShallGCEvenIfEditorExists()
257: throws Exception {
258: PropertyDescriptor feature = new PropertyDescriptor("prop",
259: this .getClass());
260: feature.setPropertyEditorClass(Ed.class);
261: DefaultPropertyModel model = new DefaultPropertyModel(this ,
262: feature);
263:
264: PropertyPanel pp = new PropertyPanel(model,
265: PropertyPanel.PREF_CUSTOM_EDITOR);
266: addToPanel(pp);
267:
268: assertTrue("Ed editor created",
269: pp.getPropertyEditor() instanceof Ed);
270:
271: Ed ed = (Ed) pp.getPropertyEditor();
272: assertNotNull("Environment has been attached", ed.env);
273:
274: //
275: // Make sure that the panel listens on changes in env
276: //
277: Listener panelListener = new Listener();
278:
279: pp.addPropertyChangeListener(panelListener);
280: ed.env.setState(PropertyEnv.STATE_INVALID);
281: panelListener.assertChanges("Change notified in panel", 1, 0);
282:
283: removeFromPanel(pp);
284: pp.removePropertyChangeListener(panelListener);
285:
286: WeakReference weak = new WeakReference(pp);
287: pp = null;
288: model = null;
289: feature = null;
290:
291: assertGC(
292: "Panel should disappear even if we have reference to property editor",
293: weak);
294: }
295:
296: public void testCompatibilityWhenUsingNodePropertyAndAskingForPropertyModel()
297: throws Exception {
298: final Ed editor = new Ed();
299:
300: class NP extends org.openide.nodes.Node.Property {
301: private Object value;
302:
303: public NP() {
304: super (Runnable.class);
305: }
306:
307: public Object getValue() {
308: return value;
309: }
310:
311: public void setValue(Object o) {
312: this .value = o;
313: }
314:
315: public boolean canWrite() {
316: return true;
317: }
318:
319: public boolean canRead() {
320: return true;
321: }
322:
323: public java.beans.PropertyEditor getPropertyEditor() {
324: return editor;
325: }
326: }
327:
328: NP property = new NP();
329: PropertyPanel panel = new PropertyPanel(property);
330:
331: assertEquals("The property is mine", property, panel
332: .getProperty());
333: assertEquals("Editor is delegated", editor, panel
334: .getPropertyEditor());
335: assertNotNull("There is a model", panel.getModel());
336: assertEquals("Type is delegated", Runnable.class, panel
337: .getModel().getPropertyType());
338:
339: Listener listener = new Listener();
340: PropertyModel model = panel.getModel();
341: model.addPropertyChangeListener(listener);
342: panel.getProperty().setValue(this );
343:
344: assertEquals("Value changed in model", this , model.getValue());
345: assertEquals("Value changed in prop", this , panel.getProperty()
346: .getValue());
347: }
348:
349: public void testCompatibilityWhenUsingPropertyModelAndAskingForNodeProperty()
350: throws Exception {
351: class PM implements PropertyModel {
352: private Object value;
353: private PropertyChangeListener listener;
354:
355: public PM() {
356: }
357:
358: public void addPropertyChangeListener(
359: PropertyChangeListener l) {
360: assertNull("Support for only one listener is here now",
361: listener);
362: listener = l;
363: }
364:
365: public void removePropertyChangeListener(
366: PropertyChangeListener l) {
367: assertEquals("Removing the one added", listener, l);
368: listener = null;
369: }
370:
371: public Class getPropertyType() {
372: return Runnable.class;
373: }
374:
375: public Object getValue() {
376: return value;
377: }
378:
379: public void setValue(Object o) {
380: Object old = value;
381:
382: this .value = o;
383: if (listener != null) {
384: listener.propertyChange(new PropertyChangeEvent(
385: this , "value", old, o));
386: }
387: }
388:
389: /*
390: public boolean canWrite() {
391: return true;
392: }
393:
394: public boolean canRead() {
395: return true;
396: }
397: */
398:
399: public Class getPropertyEditorClass() {
400: return Ed.class;
401: }
402: }
403:
404: PM model = new PM();
405: PropertyPanel panel = new PropertyPanel(model, 0);
406:
407: assertEquals("The model is mine", model, panel.getModel());
408: assertEquals("Editor is delegated", Ed.class, panel
409: .getPropertyEditor().getClass());
410: assertNotNull("There is a property", panel.getProperty());
411: assertEquals("Type is delegated", Runnable.class, panel
412: .getProperty().getValueType());
413:
414: panel.getProperty().setValue(this );
415: assertEquals("Value changed in model", this , model.getValue());
416: assertEquals("Value changed in prop", this , panel.getProperty()
417: .getValue());
418:
419: model.setValue(model);
420: assertEquals("Value change propagated into prop", model, panel
421: .getProperty().getValue());
422: }
423:
424: /** Tests a truly perverse abuse of PropertyPanel that is needed for backward
425: * compatibility. The editor colorings dialog and diff engine custom editor
426: * dialogs both can cause the PropertyPanel which invoked them to be removed
427: * from the AWT hierarchy and destroyed. <strong>But</strong> the code expects
428: * changes to continue being propagated afterward <strong>and</strong> that
429: * this not cause a memory leak(!!!).
430: *
431: * @see org.openide.explorer.propertysheet.CustomEditorDisplayer.Spud */
432: public void testPropertyPanelPropagatesChangesEvenWhenItDoesntExist()
433: throws Exception {
434: class PM implements PropertyModel {
435: private Object value;
436: private PropertyChangeListener listener = null;
437: private PropertyChangeListener listener2 = null;
438:
439: public PM() {
440: }
441:
442: public void addPropertyChangeListener(
443: PropertyChangeListener l) {
444: if (listener != null) {
445: listener2 = l;
446: } else {
447: listener = l;
448: }
449: }
450:
451: public void removePropertyChangeListener(
452: PropertyChangeListener l) {
453: if (l == listener) {
454: listener = null;
455: return;
456: }
457: if (l == listener2) {
458: listener2 = null;
459: return;
460: }
461: fail("Tried to remove a listener that was never attached: "
462: + l);
463: }
464:
465: public Class getPropertyType() {
466: return Runnable.class;
467: }
468:
469: public Object getValue() {
470: return value;
471: }
472:
473: public void setValue(Object o) {
474: Object old = value;
475: this .value = o;
476: if (listener != null) {
477: listener.propertyChange(new PropertyChangeEvent(
478: this , "value", old, o));
479: }
480: if (listener2 != null) {
481: listener2.propertyChange(new PropertyChangeEvent(
482: this , "value", old, o));
483: }
484: assertTrue("Some listener should still be listenening",
485: listener != null || listener2 != null);
486: }
487:
488: public void assertValueChangedTo(Object o) throws Exception {
489: assertSame(
490: "Value should have been updated even though property panel doesn't exist",
491: value, o);
492: }
493:
494: public Class getPropertyEditorClass() {
495: return Ed.class;
496: }
497: }
498:
499: PM model = new PM();
500: PropertyPanel pp = new PropertyPanel(model,
501: PropertyPanel.PREF_CUSTOM_EDITOR);
502:
503: addToPanel(pp);
504:
505: assertTrue("Ed editor created",
506: pp.getPropertyEditor() instanceof Ed);
507:
508: Ed ed = (Ed) pp.getPropertyEditor();
509:
510: removeFromPanel(pp);
511:
512: WeakReference weak = new WeakReference(pp);
513: pp = null;
514:
515: Runnable toTest = new Runnable() {
516: public void run() {
517: }
518: };
519:
520: ed.setValue(toTest);
521:
522: model.assertValueChangedTo(toTest);
523:
524: }
525:
526: /** Listener that counts changes.
527: */
528: private static final class Listener implements
529: PropertyChangeListener, VetoableChangeListener {
530: public boolean shallVeto;
531:
532: private int veto;
533: private int change;
534:
535: public void assertChanges(String t, int c, int v) {
536: if (c != -1) {
537: assertEquals(t + " [propertychange]", c, change);
538: }
539:
540: if (v != -1) {
541: assertEquals(t + " [vetochange]", v, veto);
542: }
543:
544: change = 0;
545: veto = 0;
546: }
547:
548: public void propertyChange(
549: java.beans.PropertyChangeEvent propertyChangeEvent) {
550: change++;
551: }
552:
553: public void vetoableChange(
554: java.beans.PropertyChangeEvent propertyChangeEvent)
555: throws java.beans.PropertyVetoException {
556: if (shallVeto) {
557: shallVeto = false;
558: PropertyVetoException e = new PropertyVetoException(
559: "Veto", propertyChangeEvent);
560:
561: // marks this exception as one that we do not want to notify
562: PropertyDialogManager.doNotNotify(e);
563: throw e;
564: }
565:
566: veto++;
567: }
568:
569: }
570:
571: /** Sample property editor.
572: */
573: private static final class Ed extends
574: java.beans.PropertyEditorSupport implements
575: ExPropertyEditor {
576: public PropertyEnv env;
577:
578: public Ed() {
579: }
580:
581: public void addPropertyChangeListener(PropertyChangeListener pcl) {
582: super .addPropertyChangeListener(pcl);
583: }
584:
585: public void attachEnv(PropertyEnv env) {
586: this .env = env;
587: }
588:
589: //The two methods below are added because, in the property panel
590: //rewrite, the property panel uses polling with a ReusablePropertyEnv
591: //to determine valid state for editors that do not support a custom
592: //editor - and the PropertyPanel cannot be initialized into custom
593: //editor mode for a property editor that doesn't actually support
594: //custom editors
595: public boolean supportsCustomEditor() {
596: return true;
597: }
598:
599: //To avoid NPE when propertypanel tries to add the custom editor
600: public Component getCustomEditor() {
601: JPanel result = new JPanel();
602: result.setBackground(java.awt.Color.ORANGE);
603: return result;
604: }
605: }
606:
607: }
|