jgoodies-binding-2.1.0/0000755000175000017500000000000011374522114014663 5ustar twernertwernerjgoodies-binding-2.1.0/src/0000755000175000017500000000000011374522114015452 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/0000755000175000017500000000000011374522114016431 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/com/0000755000175000017500000000000011374522114017207 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/com/jgoodies/0000755000175000017500000000000011374522114021012 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/0000755000175000017500000000000011374522114022424 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/0000755000175000017500000000000011374522114023566 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/ToggleButtonAdapterTest.java0000644000175000017500000002613611374522114031217 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import junit.framework.TestCase; import com.jgoodies.binding.adapter.ToggleButtonAdapter; import com.jgoodies.binding.beans.PropertyAdapter; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.event.ChangeReport; import com.jgoodies.binding.tests.event.ItemChangeReport; import com.jgoodies.binding.tests.value.RejectingValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * A test case for class {@link ToggleButtonAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.14 $ */ public final class ToggleButtonAdapterTest extends TestCase { // Constructor Tests ****************************************************** public void testConstructorRejectsNullSubject() { try { new ToggleButtonAdapter(null); fail("ToggleButtonAdapter(ValueModel) failed to reject a null subject."); } catch (NullPointerException ex) { // The expected behavior } try { new ToggleButtonAdapter(null, "one", "two"); fail("ToggleButtonAdapter(ValueModel, Object, Object) failed to reject a null subject."); } catch (NullPointerException ex) { // The expected behavior } } public void testConstructorRejectsIdenticalSelectionAndDeselectionValues() { Object value = "selected"; try { new ToggleButtonAdapter(new ValueHolder(), value, value); fail("ToggleButtonAdapter(ValueModel, Object, Object) must reject identical values for selected and deselected."); } catch (IllegalArgumentException ex) { // The expected behavior } try { new ToggleButtonAdapter(new ValueHolder(), new Integer(1), new Integer(1)); fail("ToggleButtonAdapter(ValueModel, Object, Object) must reject equal values for selected and deselected."); } catch (IllegalArgumentException ex) { // The expected behavior } try { new ToggleButtonAdapter(new ValueHolder(), null, null); fail("ToggleButtonAdapter(ValueModel, Object, Object) must reject null for both the selected and deselected value."); } catch (IllegalArgumentException ex) { // The expected behavior } } // Basic Adapter Features ************************************************* public void testAdaptsReadWriteBooleanProperty() { TestBean bean = new TestBean(); bean.setReadWriteBooleanProperty(true); ValueModel subject = new PropertyAdapter(bean, "readWriteBooleanProperty", true); ToggleButtonAdapter adapter = new ToggleButtonAdapter(subject); // Reading assertTrue("Adapter is selected.", adapter.isSelected()); bean.setReadWriteBooleanProperty(false); assertFalse("Adapter is deselected.", adapter.isSelected()); bean.setReadWriteBooleanProperty(true); assertTrue("Adapter is selected again.", adapter.isSelected()); // Writing adapter.setSelected(false); assertFalse("Adapted property is false.", bean.isReadWriteBooleanProperty()); adapter.setSelected(true); assertTrue("Adapted property is true.", bean.isReadWriteBooleanProperty()); } public void testReadWriteOperations() { testReadWriteOperations(null, "one", "two"); testReadWriteOperations(null, null, "two"); testReadWriteOperations(null, "one", null); testReadWriteOperations("one", "one", "two"); testReadWriteOperations("one", null, "two"); testReadWriteOperations("one", "one", null); testReadWriteOperations("two", "one", "two"); testReadWriteOperations("two", null, "two"); testReadWriteOperations("two", "one", null); } private void testReadWriteOperations( Object initialValue, Object selectedValue, Object deselectedValue) { ValueModel subject = new ValueHolder(initialValue); ToggleButtonAdapter adapter = new ToggleButtonAdapter( subject, selectedValue, deselectedValue); // Reading assertEquals("Adapter selection reflects the initial subject value.", Objects.equals(initialValue, selectedValue), adapter.isSelected()); subject.setValue(selectedValue); assertTrue("Adapter is selected again.", adapter.isSelected()); subject.setValue(deselectedValue); assertFalse("Adapter is deselected.", adapter.isSelected()); // Writing adapter.setSelected(true); assertEquals("The subject value is the selected value.", selectedValue, subject.getValue()); adapter.setSelected(true); assertEquals("The subject value is still the selected value.", selectedValue, subject.getValue()); adapter.setSelected(false); assertEquals("The subject value is the deselected value.", deselectedValue, subject.getValue()); adapter.setSelected(false); assertEquals("The subject value is still the deselected value.", deselectedValue, subject.getValue()); } // Change and Item Events ************************************************* public void testFiresChangeAndItemEvents() { Object selectedValue = "selected"; Object deselectedValue = "deselected"; TestBean bean = new TestBean(); bean.setReadWriteObjectProperty(selectedValue); ValueModel subject = new PropertyAdapter(bean, "readWriteObjectProperty", true); ToggleButtonAdapter adapter = new ToggleButtonAdapter(subject, selectedValue, deselectedValue); ChangeReport changeReport = new ChangeReport(); ItemChangeReport itemChangeReport = new ItemChangeReport(); adapter.addChangeListener(changeReport); adapter.addItemListener(itemChangeReport); bean.setReadWriteObjectProperty(deselectedValue); assertEquals("Deselecting the property fires a ChangeEvent.", 1, changeReport.eventCount()); assertEquals("Deselecting the property fires an ItemChangeEvent.", 1, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates deselected.", itemChangeReport.isLastStateChangeDeselected()); bean.setReadWriteObjectProperty(selectedValue); assertEquals("Selecting the property fires another ChangeEvent.", 2, changeReport.eventCount()); assertEquals("Selecting the property fires another ItemChangeEvent.", 2, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates selected.", itemChangeReport.isLastStateChangeSelected()); adapter.setSelected(false); assertEquals("Deselecting the adapter fires a single ChangeEvent.", 3, changeReport.eventCount()); assertEquals("Deselecting the adapter fires a single ItemChangeEvent.", 3, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates deselected.", itemChangeReport.isLastStateChangeDeselected()); adapter.setSelected(true); assertEquals("Selecting the adapter fires another ChangeEvent.", 4, changeReport.eventCount()); assertEquals("Selecting the adapter fires another ItemChangeEvent.", 4, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates selected.", itemChangeReport.isLastStateChangeSelected()); } // Read-Only Model ******************************************************** public void testAdaptsReadOnlyObjectProperty() { Object selectedValue = "selected"; Object deselectedValue = "deselected"; TestBean bean = new TestBean(); bean.fireChangeOnReadOnlyObjectProperty(selectedValue); ValueModel subject = new PropertyAdapter(bean, "readOnlyObjectProperty", true); ToggleButtonAdapter adapter = new ToggleButtonAdapter(subject, selectedValue, deselectedValue); assertTrue("Adapter is selected.", adapter.isSelected()); bean.fireChangeOnReadOnlyObjectProperty(deselectedValue); assertFalse("Adapter is deselected.", adapter.isSelected()); bean.fireChangeOnReadOnlyObjectProperty(selectedValue); assertTrue("Adapter is selected again.", adapter.isSelected()); } // Re-Synchronizes if the Subject Change is Rejected ********************** public void testResynchronizesAfterRejectedSubjectChange() { ValueModel selectionHolder = new ValueHolder(Boolean.TRUE); ValueModel rejectingValueHolder = new RejectingValueModel(selectionHolder); ToggleButtonAdapter adapter = new ToggleButtonAdapter(rejectingValueHolder); assertTrue("Adapter is selected.", adapter.isSelected()); adapter.setSelected(false); assertTrue("Adapter is still selected.", adapter.isSelected()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/BufferedValueModelTest.java0000644000175000017500000012311211374522114030771 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import junit.framework.TestCase; import com.jgoodies.binding.beans.PropertyAdapter; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.tests.value.CloningValueHolder; import com.jgoodies.binding.tests.value.ToUpperCaseStringHolder; import com.jgoodies.binding.value.BufferedValueModel; import com.jgoodies.binding.value.Trigger; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * Tests class {@link BufferedValueModel}. * Critical are state changes to and from the state * where buffered value == subject value. * * @author Jeanette Winzenburg * @author Karsten Lentzsch * @version $Revision: 1.22 $ * * @see BufferedValueModel */ public final class BufferedValueModelTest extends TestCase { private static final Object INITIAL_VALUE = "initial value"; private static final Object RESET_VALUE = "reset value"; /** * Holds a subject that can be reused by tests. */ private ValueModel subject; /** * Holds a trigger channel that can be reused by tests * and changed by invoking #commit and #flush. */ private Trigger triggerChannel; // Testing Proper Values ************************************************** /** * Tests that the BufferedValueModel returns the subject's values * as long as no value has been assigned. */ public void testReturnsSubjectValueIfNoValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); assertEquals( "Buffer value equals the subject value before any changes.", buffer.getValue(), subject.getValue()); subject.setValue("change1"); assertEquals( "Buffer value equals the subject value changes as long as no value has been assigned.", buffer.getValue(), subject.getValue()); subject.setValue(null); assertEquals( "Buffer value equals the subject value changes as long as no value has been assigned.", buffer.getValue(), subject.getValue()); subject.setValue("change2"); assertEquals( "Buffer value equals the subject value changes as long as no value has been assigned.", buffer.getValue(), subject.getValue()); } /** * Tests that the BufferedValueModel returns the buffered values * once a value has been assigned. */ public void testReturnsBufferedValueIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object newValue1 = subject.getValue(); buffer.setValue(newValue1); subject.setValue("subject1"); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue1); Object newValue2 = "change1"; buffer.setValue(newValue2); subject.setValue("subject2"); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue2); Object newValue3 = null; buffer.setValue(newValue3); subject.setValue(null); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue3); Object newValue4 = "change2"; buffer.setValue(newValue4); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue4); } /** * Tests that the BufferedValueModel returns the buffered values * once a value has been assigned and ignores subject value changes. */ public void testIgnoresSubjectValuesIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object newValue1 = "change1"; buffer.setValue(newValue1); subject.setValue("change3"); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue1); subject.setValue(newValue1); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue1); subject.setValue(null); assertSame( "Buffer value == new value once a value has been assigned.", buffer.getValue(), newValue1); } /** * Tests that the BufferedValueModel returns the subject's values * after a commit. */ public void testReturnsSubjectValueAfterCommit() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); // shall buffer now commit(); assertEquals( "Buffer value equals the subject value after a commit.", buffer.getValue(), subject.getValue()); subject.setValue("change2"); assertEquals( "Buffer value equals the subject value after a commit.", buffer.getValue(), subject.getValue()); subject.setValue(buffer.getValue()); assertEquals( "Buffer value equals the subject value after a commit.", buffer.getValue(), subject.getValue()); } /** * Tests that the BufferedValueModel returns the subject's values * after a flush. */ public void testReturnsSubjectValueAfterFlush() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); // shall buffer now flush(); assertEquals( "Buffer value equals the subject value after a flush.", subject.getValue(), buffer.getValue()); subject.setValue("change2"); assertEquals( "Buffer value equals the subject value after a flush.", subject.getValue(), buffer.getValue()); } // Testing Proper Value Commit and Flush ********************************** /** * Tests the core of the buffering feature: buffer modifications * do not affect the subject before a commit. */ public void testSubjectValuesUnchangedBeforeCommit() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object oldSubjectValue = subject.getValue(); buffer.setValue("changedBuffer1"); assertEquals( "Buffer changes do not change the subject value before a commit.", subject.getValue(), oldSubjectValue ); buffer.setValue(null); assertEquals( "Buffer changes do not change the subject value before a commit.", subject.getValue(), oldSubjectValue ); buffer.setValue(oldSubjectValue); assertEquals( "Buffer changes do not change the subject value before a commit.", subject.getValue(), oldSubjectValue ); buffer.setValue("changedBuffer2"); assertEquals( "Buffer changes do not change the subject value before a commit.", subject.getValue(), oldSubjectValue ); } /** * Tests the core of a commit: buffer changes are written through on commit * and change the subject value. */ public void testCommitChangesSubjectValue() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object oldSubjectValue = subject.getValue(); Object newValue1 = "change1"; buffer.setValue(newValue1); assertEquals( "Subject value is unchanged before the first commit.", subject.getValue(), oldSubjectValue); commit(); assertEquals( "Subject value is the new value after the first commit.", subject.getValue(), newValue1); // Set the buffer to the current subject value to check whether // the starts buffering, even if there's no value difference. Object newValue2 = subject.getValue(); buffer.setValue(newValue2); commit(); assertEquals( "Subject value is the new value after the second commit.", subject.getValue(), newValue2); } /** * Tests the core of a flush action: buffer changes are overridden * by subject changes after a flush. */ public void testFlushResetsTheBufferedValue() { BufferedValueModel buffer = createDefaultBufferedValueModel(); Object newValue1 = "new value1"; buffer.setValue(newValue1); assertSame( "Buffer value reflects changes before the first flush.", buffer.getValue(), newValue1); flush(); assertEquals( "Buffer value is the subject value after the first flush.", buffer.getValue(), subject.getValue()); // Set the buffer to the current subject value to check whether // the starts buffering, even if there's no value difference. Object newValue2 = subject.getValue(); buffer.setValue(newValue2); assertSame( "Buffer value reflects changes before the flush.", buffer.getValue(), newValue2); flush(); assertEquals( "Buffer value is the subject value after the second flush.", buffer.getValue(), subject.getValue()); } // Tests a Proper Buffering State ***************************************** /** * Tests that a buffer isn't buffering as long as no value has been assigned. */ public void testIsNotBufferingIfNoValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); assertFalse( "Initially the buffer does not buffer.", buffer.isBuffering()); Object newValue = "change1"; subject.setValue(newValue); assertFalse( "Subject changes do not affect the buffering state.", buffer.isBuffering()); subject.setValue(null); assertFalse( "Subject change to null does not affect the buffering state.", buffer.isBuffering()); } /** * Tests that the buffer is buffering once a value has been assigned, * even if the buffered value is equal to the subject's value. */ public void testIsBufferingIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); assertTrue( "Setting a value (even the subject's value) turns on buffering.", buffer.isBuffering()); buffer.setValue("change2"); assertTrue( "Changing the value doesn't affect the buffering state.", buffer.isBuffering()); buffer.setValue(subject.getValue()); assertTrue( "Resetting the value to the subject's value doesn't affect buffering.", buffer.isBuffering()); } /** * Tests that the buffer is not buffering after a commit. */ public void testIsNotBufferingAfterCommit() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); commit(); assertFalse( "The buffer does not buffer after a commit.", buffer.isBuffering()); Object newValue = "change1"; subject.setValue(newValue); assertFalse( "The buffer does not buffer after a commit and subject change1.", buffer.isBuffering()); subject.setValue(null); assertFalse( "The buffer does not buffer after a commit and subject change2.", buffer.isBuffering()); } /** * Tests that the buffer is not buffering after a flush. */ public void testIsNotBufferingAfterFlush() { BufferedValueModel buffer = createDefaultBufferedValueModel(); buffer.setValue("change1"); flush(); assertFalse( "The buffer does not buffer after a flush.", buffer.isBuffering()); Object newValue = "change1"; subject.setValue(newValue); assertFalse( "The buffer does not buffer after a flush and subject change1.", buffer.isBuffering()); subject.setValue(null); assertFalse( "The buffer does not buffer after a flush and subject change2.", buffer.isBuffering()); } /** * Tests that changing the buffering state fires changes of * the buffering property. */ public void testFiresBufferingChanges() { Trigger trigger2 = new Trigger(); BufferedValueModel buffer = new BufferedValueModel(subject, trigger2); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addPropertyChangeListener("buffering", changeReport); assertEquals("Initial state.", 0, changeReport.eventCount()); buffer.getValue(); assertEquals("Reading initial value.", 0, changeReport.eventCount()); buffer.setSubject(new ValueHolder()); assertEquals("After subject change.", 0, changeReport.eventCount()); buffer.setTriggerChannel(triggerChannel); assertEquals("After trigger channel change.", 0, changeReport.eventCount()); buffer.setValue("now buffering"); assertEquals("After setting the first value.", 1, changeReport.eventCount()); buffer.setValue("still buffering"); assertEquals("After setting the second value.", 1, changeReport.eventCount()); buffer.getValue(); assertEquals("Reading buffered value.", 1, changeReport.eventCount()); commit(); assertEquals("After committing.", 2, changeReport.eventCount()); buffer.getValue(); assertEquals("Reading unbuffered value.", 2, changeReport.eventCount()); buffer.setValue("buffering again"); assertEquals("After second buffering switch.", 3, changeReport.eventCount()); flush(); assertEquals("After flushing.", 4, changeReport.eventCount()); buffer.getValue(); assertEquals("Reading unbuffered value.", 4, changeReport.eventCount()); } // Tests Subject Changes ************************************************** /** * Checks that #setSubject changes the subject. */ public void testSubjectChange() { ValueHolder subject1 = new ValueHolder(); ValueHolder subject2 = new ValueHolder(); BufferedValueModel buffer = new BufferedValueModel(null, triggerChannel); assertNull( "Subject is null if not set in constructor.", buffer.getSubject()); buffer.setSubject(subject1); assertSame( "Subject has been changed.", buffer.getSubject(), subject1); buffer.setSubject(subject2); assertSame( "Subject has been changed.", buffer.getSubject(), subject2); buffer.setSubject(null); assertNull( "Subject has been changed to null.", buffer.getSubject()); } public void testSetValueSendsProperValueChangeEvents() { Object obj1 = new Integer(1); Object obj2a = new Integer(2); Object obj2b = new Integer(2); testSetValueFiresProperEvents(null, obj1, true); testSetValueFiresProperEvents(obj1, null, true); testSetValueFiresProperEvents(obj1, obj1, false); testSetValueFiresProperEvents(obj1, obj2a, true); testSetValueFiresProperEvents(obj2a, obj2b, true); // identity test testSetValueFiresProperEvents(null, null, false); } public void testSetValueFiresProperValueChangeEvents() { Object obj1 = new Integer(1); Object obj2a = new Integer(2); Object obj2b = new Integer(2); testValueChangeSendsProperEvents(null, obj1, true); testValueChangeSendsProperEvents(obj1, null, true); testValueChangeSendsProperEvents(obj1, obj1, false); testValueChangeSendsProperEvents(obj1, obj2a, true); testValueChangeSendsProperEvents(obj2a, obj2b, true); // identity test testValueChangeSendsProperEvents(null, null, false); } public void testSetValueFiresIfNewValueAndSubjectChange() { Object initialValue = new Integer(0); Object oldValue = new Integer(1); Object newValue = new Integer(2); subject.setValue(initialValue); BufferedValueModel buffer = new BufferedValueModel(subject, new Trigger()); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); buffer.setValue(oldValue); assertTrue("Buffer is now buffering", buffer.isBuffering()); assertEquals("Buffer fired a value change.", 1, changeReport.eventCount()); subject.setValue(newValue); assertEquals("Setting the subject in buffered state fires no value change.", 1, changeReport.eventCount()); assertEquals("The buffered value is 1.", oldValue, buffer.getValue()); // Setting the buffer from 1 to 2 should fire a value change. buffer.setValue(newValue); assertEquals("The value is now 2.", newValue, buffer.getValue()); assertTrue("The buffer is still buffering", buffer.isBuffering()); assertEquals("Setting the buffer from 1 to 2 fires a value change.", 2, changeReport.eventCount()); } /** * Checks that the buffer reports the current subject's value if unbuffered. */ public void testReturnsCurrentSubjectValue() { Object value1_1 = "value1.1"; Object value1_2 = "value1.2"; Object value1_3 = "value1.3"; Object value2_1 = "value2.1"; Object value2_2 = "value2.2"; ValueHolder subject1 = new ValueHolder(value1_1); ValueHolder subject2 = new ValueHolder(value2_1); BufferedValueModel buffer = new BufferedValueModel(subject1, triggerChannel); assertSame( "Buffer returns the subject value of the current subject1.", buffer.getValue(), subject1.getValue()); subject1.setValue(value1_2); assertSame( "Buffer returns the new subject value of the current subject1.", buffer.getValue(), subject1.getValue()); buffer.setSubject(subject2); assertSame( "Buffer returns the subject value of the current subject2.", buffer.getValue(), subject2.getValue()); subject1.setValue(value1_3); subject2.setValue(value2_2); assertSame( "Buffer returns the new subject value of the current subject2.", buffer.getValue(), subject2.getValue()); } /** * Checks that the buffer listens to changes of the current subject * and moves the value change handler if the subject changes. */ public void testListensToCurrentSubject() { Object value1_1 = "value1.1"; Object value1_2 = "value1.2"; Object value2_1 = "value2.1"; Object value2_2 = "value2.2"; ValueHolder subject1 = new ValueHolder(null); ValueHolder subject2 = new ValueHolder(null); BufferedValueModel buffer = new BufferedValueModel(subject1, triggerChannel); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); subject1.setValue(value1_1); assertEquals("Value change.", 1, changeReport.eventCount()); subject2.setValue(value2_1); assertEquals("No value change.", 1, changeReport.eventCount()); buffer.setSubject(subject2); assertEquals("Value changed because of subject change.", 2, changeReport.eventCount()); subject1.setValue(value1_2); assertEquals("No value change.", 2, changeReport.eventCount()); subject2.setValue(value2_2); assertEquals("Value change.", 3, changeReport.eventCount()); } // Trigger Channel Tests ************************************************* /** * Checks that the trigger channel is non-null. */ public void testRejectNullTriggerChannel() { try { new BufferedValueModel(subject, null); fail("The constructor must reject a null trigger channel."); } catch (NullPointerException e) { // The expected behavior } BufferedValueModel buffer = createDefaultBufferedValueModel(); try { buffer.setTriggerChannel(null); fail("The trigger channel setter must reject null values."); } catch (NullPointerException e) { // The expected behavior } } /** * Checks that #setTriggerChannel changes the trigger channel. */ public void testTriggerChannelChange() { ValueHolder trigger1 = new ValueHolder(); ValueHolder trigger2 = new ValueHolder(); BufferedValueModel buffer = new BufferedValueModel(subject, trigger1); assertSame( "Trigger channel has been changed.", buffer.getTriggerChannel(), trigger1); buffer.setTriggerChannel(trigger2); assertSame( "Trigger channel has been changed.", buffer.getTriggerChannel(), trigger2); } /** * Checks and verifies that commit and flush events are driven * by the current trigger channel. */ public void testListensToCurrentTriggerChannel() { ValueHolder trigger1 = new ValueHolder(); ValueHolder trigger2 = new ValueHolder(); BufferedValueModel buffer = new BufferedValueModel(subject, trigger1); buffer.setValue("change1"); Object subjectValue = subject.getValue(); Object bufferedValue = buffer.getValue(); trigger2.setValue(true); assertEquals( "Changing the unrelated trigger2 to true has no effect on the subject.", subject.getValue(), subjectValue); assertSame( "Changing the unrelated trigger2 to true has no effect on the buffer.", buffer.getValue(), bufferedValue); trigger2.setValue(false); assertEquals( "Changing the unrelated trigger2 to false has no effect on the subject.", subject.getValue(), subjectValue); assertSame( "Changing the unrelated trigger2 to false has no effect on the buffer.", buffer.getValue(), bufferedValue); // Change the trigger channel to trigger2. buffer.setTriggerChannel(trigger2); assertSame( "Trigger channel has been changed.", buffer.getTriggerChannel(), trigger2); trigger1.setValue(true); assertEquals( "Changing the unrelated trigger1 to true has no effect on the subject.", subject.getValue(), subjectValue); assertSame( "Changing the unrelated trigger1 to true has no effect on the buffer.", buffer.getValue(), bufferedValue); trigger1.setValue(false); assertEquals( "Changing the unrelated trigger1 to false has no effect on the subject.", subject.getValue(), subjectValue); assertSame( "Changing the unrelated trigger1 to false has no effect on the buffer.", buffer.getValue(), bufferedValue); // Commit using trigger2. trigger2.setValue(true); assertEquals( "Changing the current trigger2 to true commits the buffered value.", buffer.getValue(), subject.getValue()); buffer.setValue("change2"); subjectValue = subject.getValue(); trigger2.setValue(false); assertEquals( "Changing the current trigger2 to false flushes the buffered value.", buffer.getValue(), subject.getValue()); assertEquals( "Changing the current trigger2 to false flushes the buffered value.", buffer.getValue(), subjectValue); } // Tests Proper Update Notifications ************************************** /** * Checks that subject changes fire value changes * if no value has been assigned. */ public void testPropagatesSubjectChangesIfNoValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); subject.setValue("change1"); assertEquals("Value change.", 1, changeReport.eventCount()); subject.setValue(null); assertEquals("Value change.", 2, changeReport.eventCount()); subject.setValue("change2"); assertEquals("Value change.", 3, changeReport.eventCount()); subject.setValue(buffer.getValue()); assertEquals("No value change.", 3, changeReport.eventCount()); } /** * Tests that subject changes are not propagated once a value has * been assigned, i.e. the buffer is buffering. */ public void testIgnoresSubjectChangesIfValueAssigned() { BufferedValueModel buffer = createDefaultBufferedValueModel(); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.setValue("new buffer"); buffer.addValueChangeListener(changeReport); subject.setValue("change1"); assertEquals("Value change.", 0, changeReport.eventCount()); subject.setValue(null); assertEquals("Value change.", 0, changeReport.eventCount()); subject.setValue("change2"); assertEquals("Value change.", 0, changeReport.eventCount()); subject.setValue(buffer.getValue()); assertEquals("No value change.", 0, changeReport.eventCount()); } /** * Checks and verifies that a commit fires no value change. */ public void testCommitFiresNoChangeOnSameOldAndNewValues() { BufferedValueModel buffer = createDefaultBufferedValueModel( new ValueHolder()); buffer.setValue("value1"); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); assertEquals("No initial change.", 0, changeReport.eventCount()); commit(); assertEquals("First commit: no change.", 0, changeReport.eventCount()); buffer.setValue("value2"); assertEquals("Setting a value: a change.", 1, changeReport.eventCount()); commit(); assertEquals("Second commit: no change.", 1, changeReport.eventCount()); } public void testCommitFiresChangeOnDifferentOldAndNewValues() { BufferedValueModel buffer = createDefaultBufferedValueModel( new ToUpperCaseStringHolder()); buffer.setValue("initialValue"); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); buffer.setValue("value1"); assertEquals("One event fired", 1, changeReport.eventCount()); assertEquals("First value set.", "value1", changeReport.lastNewValue()); commit(); assertEquals("Commit fires if the subject modifies the value.", 2, changeReport.eventCount()); assertEquals("Old value is the buffered value.", "value1", changeReport.lastOldValue()); assertEquals("New value is the modified value.", "VALUE1", changeReport.lastNewValue()); } /** * Tests that a flush event fires a value change if and only if * the flushed value does not equal the buffered value. */ public void testFlushFiresTrueValueChanges() { BufferedValueModel buffer = createDefaultBufferedValueModel(new ValueHolder()); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.setValue("new buffer"); subject.setValue("new subject"); buffer.addValueChangeListener(changeReport); flush(); assertEquals("First flush changes value.", 1, changeReport.eventCount()); buffer.setValue(subject.getValue()); assertEquals("Resetting value: no change.", 1, changeReport.eventCount()); flush(); assertEquals("Second flush: no change.", 1, changeReport.eventCount()); buffer.setValue("new buffer2"); assertEquals("Second value change.", 2, changeReport.eventCount()); subject.setValue("new subject2"); assertEquals("Setting new subject value: no change.", 2, changeReport.eventCount()); buffer.setValue(subject.getValue()); assertEquals("Third value change.", 3, changeReport.eventCount()); flush(); assertEquals("Third flush: no change.", 3, changeReport.eventCount()); } public void testFlushChecksIdentity() { List list1 = new ArrayList(); List list2 = new LinkedList(); BufferedValueModel buffer = createDefaultBufferedValueModel( new ValueHolder()); subject.setValue(list1); buffer.setValue(list2); assertSame("Buffered value is list2.", list2, buffer.getValue()); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); flush(); assertSame("After flush, the value is list1.", list1, buffer.getValue()); assertEquals("Flush fires an event for different subject value and buffer.", 1, changeReport.eventCount()); assertSame("The event's old value is list2.", list2, changeReport.lastOldValue()); assertSame("The event's new value is list1.", list1, changeReport.lastNewValue()); } public void testValueNoficationAfterSubjectChanged() { ValueModel initialSubject = new ValueHolder("initial value"); BufferedValueModel buffer = new BufferedValueModel(initialSubject, new Trigger()); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); int eventCount = 0; Object newValue = "changed"; initialSubject.setValue(newValue); assertEquals( "Value changed on original subject", ++eventCount, changeReport.eventCount()); ValueModel subjectWithSame = new ValueHolder(newValue); buffer.setSubject(subjectWithSame); assertEquals( "Subject changed but same value as old", eventCount, changeReport.eventCount()); ValueModel subject2 = new ValueHolder("changedSubjectWithDifferentValue"); buffer.setSubject(subject2); assertEquals( "Value changed by changing the subject with different value", ++eventCount, changeReport.eventCount()); } // Rejecting Access If Subject == null ************************************ public void testRejectGetValueWhenSubjectIsNull() { BufferedValueModel buffer = new BufferedValueModel(null, triggerChannel); try { buffer.getValue(); fail("The Buffer must reject attempts to read unbuffered values when the subject is null."); } catch (NullPointerException ex) { // The expected behavior } buffer.setSubject(subject); buffer.setValue("now buffering"); buffer.setSubject(null); try { buffer.getValue(); fail("The Buffer must reject attempts to read buffered values when the subject is null."); } catch (NullPointerException ex) { // The expected behavior } } public void testRejectSetValueWhenSubjectIsNull() { BufferedValueModel buffer = new BufferedValueModel(null, triggerChannel); try { Object value = "valuewithoutsubject??"; buffer.setValue(value); fail("The Buffer must reject attempts to set values when the subject is null."); } catch (NullPointerException ex) { // The expected behavior } buffer.setSubject(subject); buffer.setValue("now buffering"); buffer.setSubject(null); try { buffer.setValue("a new value"); fail("The Buffer must reject attempts to set values when the subject is null - even if buffering."); } catch (NullPointerException ex) { // The expected behavior } } public void testRejectCommitWhenSubjectIsNull() { BufferedValueModel buffer = new BufferedValueModel(null, triggerChannel); try { commit(); fail("The Buffer must reject attempts to commit when the subject is null."); } catch (NullPointerException ex) { // The expected behavior } buffer.setSubject(subject); buffer.setValue("now buffering"); buffer.setSubject(null); try { commit(); fail("The Buffer must reject attempts to commit when the subject is null - even if buffering."); } catch (NullPointerException ex) { // The expected behavior } } public void testRejectFlushWhenSubjectIsNull() { BufferedValueModel buffer = new BufferedValueModel(null, triggerChannel); try { flush(); fail("The Buffer must reject attempts to commit when the subject is null."); } catch (NullPointerException ex) { // The expected behavior } buffer.setSubject(subject); buffer.setValue("now buffering"); buffer.setSubject(null); try { flush(); fail("The Buffer must reject attempts to commit when the subject is null - even if buffering."); } catch (NullPointerException ex) { // The expected behavior } } // Misc Tests ************************************************************* /** * Tests read actions on a read-only model. */ public void testReadOnly() { TestBean bean = new TestBean(); bean.readOnlyObjectProperty = "testString"; PropertyAdapter readOnlyModel = new PropertyAdapter(bean, "readOnlyObjectProperty"); BufferedValueModel buffer = new BufferedValueModel(readOnlyModel, triggerChannel); assertSame( "Can read values from a read-only model.", buffer.getValue(), readOnlyModel.getValue()); Object newValue1 = "new value"; buffer.setValue(newValue1); assertSame( "Can read values from a read-only model when buffering.", buffer.getValue(), newValue1); flush(); assertSame( "Can read values from a read-only model after a flush.", buffer.getValue(), bean.readOnlyObjectProperty); buffer.setValue("new value2"); try { commit(); fail("Cannot commit to a read-only model."); } catch (Exception e) { // The expected behavior } } /** * Tests write actions on a write-only model. */ public void testWriteOnly() { TestBean bean = new TestBean(); bean.writeOnlyObjectProperty = "testString"; PropertyAdapter writeOnlyModel = new PropertyAdapter(bean, "writeOnlyObjectProperty"); BufferedValueModel buffer = new BufferedValueModel(writeOnlyModel, triggerChannel); Object newValue1 = "new value"; buffer.setValue(newValue1); assertSame( "Can buffer a value on a write-only model.", buffer.getValue(), newValue1); commit(); writeOnlyModel.setValue("new value2"); try { flush(); fail("Cannot flush a value from a write-only model."); } catch (Exception e) { // The expected behavior } } // Test Implementations *************************************************** private void testSetValueFiresProperEvents(Object oldValue, Object newValue, boolean eventExpected) { BufferedValueModel valueModel = new BufferedValueModel(new ValueHolder(oldValue), new Trigger()); testFiresProperEvents(valueModel, oldValue, newValue, eventExpected); } private void testValueChangeSendsProperEvents(Object oldValue, Object newValue, boolean eventExpected) { BufferedValueModel defaultModel = createDefaultBufferedValueModel(); defaultModel.setValue(oldValue); testFiresProperEvents(defaultModel, oldValue, newValue, eventExpected); } private void testFiresProperEvents(BufferedValueModel valueModel, Object oldValue, Object newValue, boolean eventExpected) { PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addValueChangeListener(changeReport); int expectedEventCount = eventExpected ? 1 : 0; valueModel.setValue(newValue); assertEquals( "Expected event count after ( " + oldValue + " -> " + newValue + ").", expectedEventCount, changeReport.eventCount()); if (eventExpected) { assertEquals("Event's old value.", oldValue, changeReport.lastOldValue()); assertEquals("Event's new value.", newValue, changeReport.lastNewValue()); } } // Helper Code ************************************************************ private void commit() { triggerChannel.triggerCommit(); } private void flush() { triggerChannel.triggerFlush(); } private BufferedValueModel createDefaultBufferedValueModel() { subject.setValue(RESET_VALUE); return new BufferedValueModel(subject, triggerChannel); } private BufferedValueModel createDefaultBufferedValueModel(ValueModel aSubject) { this.subject = aSubject; subject.setValue(RESET_VALUE); return new BufferedValueModel(subject, triggerChannel); } // Setup / Teardown ******************************************************* /** * @throws Exception in case of an unexpected problem */ @Override protected void setUp() throws Exception { super.setUp(); subject = new CloningValueHolder(INITIAL_VALUE); triggerChannel = new Trigger(); } /** * @throws Exception in case of an unexpected problem */ @Override protected void tearDown() throws Exception { super.tearDown(); subject = null; triggerChannel = null; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/ExtendedPropertyChangeSupportTest.java0000644000175000017500000001633211374522114033306 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; import com.jgoodies.binding.beans.ExtendedPropertyChangeSupport; import com.jgoodies.binding.tests.event.PropertyChangeReport; /** * A test case for class {@link ExtendedPropertyChangeSupport}.

* * TODO: Test multicast events. * * @author Mattias Neuling * @author Karsten Lentzsch * @version $Revision: 1.13 $ */ public final class ExtendedPropertyChangeSupportTest extends TestCase { private String one; private String two; private List emptyList1; private List emptyList2; private List list1a; private List list1b; /** * @throws Exception in case of an unexpected problem */ @Override protected void setUp() throws Exception { super.setUp(); one = "one"; two = "two"; emptyList1 = new ArrayList(); emptyList2 = new ArrayList(); list1a = new ArrayList(); list1a.add(one); list1b = new ArrayList(); list1b.add(one); } /** * @throws Exception in case of an unexpected problem */ @Override protected void tearDown() throws Exception { super.tearDown(); emptyList1 = null; emptyList2 = null; list1a = null; list1b = null; } public void testCommonBehavior() { testCommon("name1", null, null, null); testCommon("name1", null, null, one); testCommon("name1", null, one, null); testCommon("name1", null, one, one); testCommon("name1", null, one, two); testCommon("name1", null, emptyList1, emptyList2); testCommon("name1", null, list1a, list1b); testCommon("name1", "name1", null, null); testCommon("name1", "name1", null, one); testCommon("name1", "name1", one, null); testCommon("name1", "name1", one, one); testCommon("name1", "name1", one, two); testCommon("name1", "name1", emptyList1, emptyList2); testCommon("name1", "name1", list1a, list1b); testCommon("name1", "name2", null, null); testCommon("name1", "name2", null, one); testCommon("name1", "name2", one, null); testCommon("name1", "name2", one, one); testCommon("name1", "name2", one, two); testCommon("name1", "name2", emptyList1, emptyList2); testCommon("name1", "name2", list1a, list1b); } public void testDifferences() { testDifference("name1", "name1", emptyList1, emptyList2); testDifference("name1", "name1", list1a, list1b); } private void testCommon( String observedPropertyName, String changedPropertyName, Object oldValue, Object newValue) { fireAndCount(true, observedPropertyName, changedPropertyName, oldValue, newValue, false, false); } private void testDifference( String observedPropertyName, String changedPropertyName, Object oldValue, Object newValue) { fireAndCount(false, observedPropertyName, changedPropertyName, oldValue, newValue, true, false); fireAndCount(false, observedPropertyName, changedPropertyName, oldValue, newValue, false, true); } private void fireAndCount( boolean countsShallBeEqual, String observedPropertyName, String changedPropertyName, Object oldValue, Object newValue, boolean checkIdentityDefault, boolean checkIdentity) { PropertyChangeReport epcsNamedCounter = new PropertyChangeReport(); PropertyChangeReport epcsMulticastCounter = new PropertyChangeReport(); PropertyChangeReport pcsNamedCounter = new PropertyChangeReport(); PropertyChangeReport pcsMulticastCounter = new PropertyChangeReport(); ExtendedPropertyChangeSupport epcs = new ExtendedPropertyChangeSupport(this, checkIdentityDefault); PropertyChangeSupport pcs = new PropertyChangeSupport(this); epcs.addPropertyChangeListener(observedPropertyName, epcsNamedCounter); epcs.addPropertyChangeListener(epcsMulticastCounter); pcs.addPropertyChangeListener(observedPropertyName, pcsNamedCounter); pcs.addPropertyChangeListener(pcsMulticastCounter); if (checkIdentity) { epcs.firePropertyChange(changedPropertyName, oldValue, newValue, true); } else { epcs.firePropertyChange(changedPropertyName, oldValue, newValue); } pcs.firePropertyChange(changedPropertyName, oldValue, newValue); boolean namedCountersAreEqual = pcsNamedCounter.eventCount() == epcsNamedCounter.eventCount(); boolean multicastCountersAreEqual = pcsMulticastCounter.eventCount() == epcsMulticastCounter.eventCount(); if (countsShallBeEqual) { assertTrue( "Named counters shall be equal", namedCountersAreEqual); assertTrue( "Multicast counters shall be equal", multicastCountersAreEqual); } else { assertFalse( "Named counters shall differ", namedCountersAreEqual); assertFalse( "Multicast counters shall differ", multicastCountersAreEqual); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/PropertyExceptionTest.java0000644000175000017500000000617011374522114031000 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.PrintWriter; import junit.framework.TestCase; import com.jgoodies.binding.beans.PropertyUnboundException; /** * A test case for class {@link com.jgoodies.binding.beans.PropertyException}. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ */ public final class PropertyExceptionTest extends TestCase { // Constructor Tests ****************************************************** public void testConstructWithoutCause() { new PropertyUnboundException("Message"); } public void testConstructWithCause() { new PropertyUnboundException("Message", new IllegalArgumentException( "The root cause's message")); } // Printing Tests ********************************************************* public void testPrintWithoutCause() { Exception e = new PropertyUnboundException("Message"); OutputStream out = new ByteArrayOutputStream(); e.printStackTrace(new PrintStream(out)); e.printStackTrace(new PrintWriter(out)); } public void testPrintWithCause() { Exception e = new PropertyUnboundException("Message", new IllegalArgumentException("The root cause's message")); OutputStream out = new ByteArrayOutputStream(); e.printStackTrace(new PrintStream(out)); e.printStackTrace(new PrintWriter(out)); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/SingleListSelectionAdapterTest.java0000644000175000017500000003257611374522114032532 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import javax.swing.DefaultListSelectionModel; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import junit.framework.TestCase; import com.jgoodies.binding.adapter.SingleListSelectionAdapter; import com.jgoodies.binding.tests.event.ListSelectionReport; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class * {@link com.jgoodies.binding.adapter.SingleListSelectionAdapter}. * * @author Karsten Lentzsch * @author Jeanette Winzenburg * @version $Revision: 1.15 $ */ public final class SingleListSelectionAdapterTest extends TestCase { /** * Holds a sequence of indices used to test index changes. */ private static final int[] INDICES = {0, -1, -1, 3, 2, -1, 1, 2, 3, 4, 0, -1}; /** * Checks that changes to the underlying ValueModel update the adapter. */ public void testAdapterReflectsModelChanges() { ValueModel model = new ValueHolder(); SingleListSelectionAdapter adapter = new SingleListSelectionAdapter(model); for (int index : INDICES) { model.setValue(new Integer(index)); assertEquals("New adapter index", index, adapter.getMinSelectionIndex()); } } /** * Checks that changes to the adapter update the underlying ValueModel. */ public void testModelReflectsAdapterChanges() { ValueModel model = new ValueHolder(); SingleListSelectionAdapter adapter = new SingleListSelectionAdapter(model); for (int index : INDICES) { adapter.setLeadSelectionIndex(index); assertEquals("New model index", new Integer(index), model.getValue()); } } /** * Checks that index changes of the underlying ValueModel are observed and * reported by the adapter.

* * Wrong test: changeReport.getNew/OldValue() != * listReport.getFirst/LastIndex() if switching between selected/unselected * */ // public void testAdapterFiresModelEvents() { // // ValueModel model = new ValueHolder(-1); // SingleListSelectionAdapter adapter = new SingleListSelectionAdapter( // model); // PropertyChangeReport changeReport = new PropertyChangeReport(); // ListSelectionReport listReport = new ListSelectionReport(); // model.addValueChangeListener(changeReport); // adapter.addListSelectionListener(listReport); // for (int i = 0; i < INDICES.length; i++) { // int index = INDICES[i]; // model.setValue(new Integer(index)); // if (changeReport.hasEvents()) { // Object oldValue = changeReport.lastEvent().getOldValue(); // Object newValue = changeReport.lastEvent().getNewValue(); // int oldIndex = (oldValue == null) ? -1 : ((Integer) oldValue) // .intValue(); // int newIndex = (newValue == null) ? -1 : ((Integer) newValue) // .intValue(); // int firstIndex = Math.min(oldIndex, newIndex); // int lastIndex = Math.max(oldIndex, newIndex); // assertEquals("Last model event index", new Integer(index), // newValue); // if (listReport.hasEvents()) { // assertEquals("Wrong test...Last adapter event first index", // firstIndex, listReport.lastEvent().getFirstIndex()); // assertEquals("Wrong test...Last adapter event last index", // lastIndex, listReport.lastEvent().getLastIndex()); // } // } // } // // // Check that the model and adapter throw the same number of events. // assertEquals("Event count", changeReport.eventCount(), listReport // .eventCount()); // } /** * Checks for correct SelectionEvent on empty --> selected. (an index of -1 * is invalid because it does not represent a position in the list that * might have changed selection state). */ public void testSelectEvent() { ValueModel model = new ValueHolder(-1); SingleListSelectionAdapter adapter = new SingleListSelectionAdapter(model); ListSelectionReport report = new ListSelectionReport(); adapter.addListSelectionListener(report); adapter.setSelectionInterval(2, 2); ListSelectionEvent e = report.lastEvent(); assertEquals("first must be index of changed selection ", 2, e.getFirstIndex()); } /** * Checks for correct SelectionEvent on selected --> empty. */ public void testDeSelectEvent() { ValueModel model = new ValueHolder(2); SingleListSelectionAdapter adapter = new SingleListSelectionAdapter(model); ListSelectionReport report = new ListSelectionReport(); adapter.addListSelectionListener(report); adapter.clearSelection(); ListSelectionEvent e = report.lastEvent(); assertEquals("first must be index of changed selection ", 2, e.getFirstIndex()); } public void testChangeSelectEvent() { ValueModel model = new ValueHolder(2); SingleListSelectionAdapter adapter = new SingleListSelectionAdapter(model); ListSelectionReport report = new ListSelectionReport(); adapter.addListSelectionListener(report); adapter.setSelectionInterval(3, 3); ListSelectionEvent e = report.lastEvent(); assertEquals("first must be lower index of changed selection ", 2, e.getFirstIndex()); assertEquals("last must be upper index of changed selection ", 3, e.getLastIndex()); } public void testIsSelectedIndex() { ListSelectionModel model = getSelectionAdapter(-1); assertTrue("selection must return empty", model.isSelectionEmpty()); assertFalse("isSelectedIndex(-1) must return false", model.isSelectedIndex(-1)); } public void testRemoveSelectionInterval() { ListSelectionModel adapter = getSelectionAdapter(1); adapter.removeSelectionInterval(0, 2); assertTrue("selection must be empty ", adapter.isSelectionEmpty()); } public void testAddSelectionInterval() { ListSelectionModel adapter = getSelectionAdapter(-1); try { adapter.addSelectionInterval(0, 2); } catch (UnsupportedOperationException ex) { fail("adapter must not fire on legal operation" + ex); } } //--------------------- insert/removeIndexInterval public void testInsertIndexIntervalBefore() { ListSelectionModel adapter = getSelectionAdapter(2); adapter.insertIndexInterval(0, 1, false); assertEquals("selectionindex must be increased by 1", 3, adapter.getMinSelectionIndex()); } public void testInsertIndexIntervalAfter() { ListSelectionModel adapter = getSelectionAdapter(2); adapter.insertIndexInterval(3, 1, false); assertEquals("selectionindex must be unchanged", 2, adapter.getMinSelectionIndex()); } public void testRemoveIndexIntervalAfter() { ListSelectionModel adapter = getSelectionAdapter(2); adapter.removeIndexInterval(3, 3); assertEquals("selectionindex must be unchanged", 2, adapter.getMinSelectionIndex()); } public void testRemoveIndexIntervalBefore() { ListSelectionModel adapter = getSelectionAdapter(2); adapter.removeIndexInterval(0, 0); assertEquals("selectionindex must be decreased by 1", 1, adapter.getMinSelectionIndex()); } public void testRemoveIndexIntervalOn() { ListSelectionModel model = getSelectionAdapter(2); model.removeIndexInterval(2, 2); assertTrue("selection must be empty", model.isSelectionEmpty()); } public void testLead() { ListSelectionModel model = getSelectionAdapter(-1); model.setSelectionInterval(0, 2); assertEquals("second parameter must be lead", 2, model.getLeadSelectionIndex()); } private ListSelectionModel getSelectionAdapter(int index) { ValueModel model = new ValueHolder(index); SingleListSelectionAdapter adapter = new SingleListSelectionAdapter(model); return adapter; } //---------------- testing reference implementation public void testReferenceInsertIndexIntervalBefore() { ListSelectionModel model = createReferenceSelection(2); model.insertIndexInterval(0, 1, false); assertEquals("selectionindex must be increased by 1", 3, model.getMinSelectionIndex()); } public void testReferenceInsertIndexIntervalAfter() { ListSelectionModel model = createReferenceSelection(2); model.insertIndexInterval(3, 1, false); assertEquals("selectionindex must be unchanged", 2, model.getMinSelectionIndex()); } public void testReferenceRemoveIndexIntervalBefore() { ListSelectionModel model = createReferenceSelection(2); model.removeIndexInterval(0, 0); assertEquals("selectionindex must be decreased by 1", 1, model.getMinSelectionIndex()); } public void testReferenceRemoveIndexIntervalAfter() { ListSelectionModel model = createReferenceSelection(2); model.removeIndexInterval(3, 3); assertEquals("selectionindex must be unchanged", 2, model.getMinSelectionIndex()); } public void testReferenceRemoveIndexIntervalOn() { ListSelectionModel model = createReferenceSelection(2); model.removeIndexInterval(2, 2); assertTrue("selection must be empty", model.isSelectionEmpty()); } public void testReferenceIsSelectedIndex() { ListSelectionModel model = createReferenceSelection(-1); assertTrue("selection must be empty", model.isSelectionEmpty()); assertFalse("isSelectedIndex(-1) must return false", model.isSelectedIndex(-1)); } public void testReferenceRemoveSelectionInterval() { ListSelectionModel adapter = createReferenceSelection(1); adapter.removeSelectionInterval(0, 2); assertTrue("selection must be empty ", adapter.isSelectionEmpty()); } public void testReferenceAddSelectionInterval() { ListSelectionModel adapter = createReferenceSelection(-1); try { adapter.addSelectionInterval(0, 2); } catch (UnsupportedOperationException ex) { fail("adapter must not fire on legal operation" + ex); } } public void testReferenceLead() { ListSelectionModel model = createReferenceSelection(-1); model.setSelectionInterval(0, 2); assertEquals("second parameter must be lead", 2, model.getLeadSelectionIndex()); } private ListSelectionModel createReferenceSelection(int i) { ListSelectionModel model = new DefaultListSelectionModel(); model.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); model.setSelectionInterval(i, i); return model; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/IndirectListModelTest.java0000644000175000017500000010157211374522114030655 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.swing.DefaultListModel; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import junit.framework.TestCase; import com.jgoodies.binding.list.IndirectListModel; import com.jgoodies.binding.tests.event.ListDataReport; import com.jgoodies.binding.tests.event.ListSizeConstraintChecker; import com.jgoodies.binding.tests.value.ValueHolderWithOldValueNull; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.collect.ArrayListModel; import com.jgoodies.common.collect.LinkedListModel; import com.jgoodies.common.collect.ObservableList; /** * A test case for class {@link IndirectListModel}. * * @author Karsten Lentzsch * @version $Revision: 1.6 $ */ public final class IndirectListModelTest extends TestCase { private static final Object[] AN_ARRAY = {"one", "two", "three"}; private DefaultListModel listModel; // Initialization ********************************************************* @Override protected void setUp() throws Exception { super.setUp(); listModel = createListModel(AN_ARRAY); } // Testing Constructors *************************************************** public void testConstructorRejectsNullListModelHolder() { try { new IndirectListModel((ValueModel) null); fail("The IndirectListModel must reject a null ListModel holder."); } catch (NullPointerException e) { // The expected behavior. } } public void testConstructorRejectsNonIdentityCheckingValueHolder() { try { new IndirectListModel(new ValueHolder(listModel)); fail("The IndirectListModel must reject ListModel holders that have the identity check disabled."); } catch (IllegalArgumentException e) { // The expected behavior. } } public void testConstructorRejectsInvalidListModelHolderContent() { try { new IndirectListModel(new ValueHolder("Hello", true)); fail("The IndirectListModel must reject ListModel holder content other than ListModel."); } catch (ClassCastException e) { // The expected behavior. } } // ************************************************************************ /** * Checks that list data events from an underlying are reported * by the SelectionInList. */ public void testFiresListModelListDataEvents() { ListDataReport listDataReport1 = new ListDataReport(); ListDataReport listDataReport2 = new ListDataReport(); ArrayListModel arrayListModel = new ArrayListModel(); IndirectListModel lmh = new IndirectListModel((ListModel) arrayListModel); arrayListModel.addListDataListener(listDataReport1); lmh.addListDataListener(listDataReport2); arrayListModel.add("One"); assertEquals("An element has been added.", listDataReport2.eventCount(), 1); assertEquals("An element has been added.", listDataReport2 .eventCountAdd(), 1); arrayListModel.addAll(Arrays.asList(new String[]{"two", "three", "four"})); assertEquals("An element block has been added.", listDataReport2.eventCount(), 2); assertEquals("An element block has been added.", listDataReport2 .eventCountAdd(), 2); arrayListModel.remove(0); assertEquals("An element has been removed.", listDataReport2.eventCount(), 3); assertEquals("No element has been added.", listDataReport2 .eventCountAdd(), 2); assertEquals("An element has been removed.", listDataReport2 .eventCountRemove(), 1); arrayListModel.set(1, "newTwo"); assertEquals("An element has been replaced.", listDataReport2.eventCount(), 4); assertEquals("No element has been added.", listDataReport2 .eventCountAdd(), 2); assertEquals("No element has been removed.", listDataReport2 .eventCountRemove(), 1); assertEquals("An element has been changed.", listDataReport2 .eventCountChange(), 1); // Compare the event counts of the list models listener // with the SelectionInList listener. assertEquals("Add event counts are equal.", listDataReport1.eventCountAdd(), listDataReport2.eventCountAdd()); assertEquals("Remove event counts are equal.", listDataReport1.eventCountRemove(), listDataReport2.eventCountRemove()); assertEquals("Change event counts are equal.", listDataReport1.eventCountChange(), listDataReport2.eventCountChange()); } // Registering, Unregistering and Registering of the ListDataListener ***** /** * Checks and verifies that the SelectionInList registers * its ListDataListener with the underlying ListModel once only. * In other words: the SelectionInList doesn't register * its ListDataListener multiple times.

* * Uses a list holder that checks the identity and * reports an old and new value. */ public void testSingleListDataListener() { testSingleListDataListener(new ValueHolder(null, true)); } /** * Checks and verifies that the SelectionInList registers * its ListDataListener with the underlying ListModel once only. * In other words: the SelectionInList doesn't register * its ListDataListener multiple times.

* * Uses a list holder uses null as old value when reporting value changes. */ public void testSingleListDataListenerNoOldList() { testSingleListDataListener(new ValueHolderWithOldValueNull(null)); } /** * Checks and verifies that the SelectionInList registers * its ListDataListener with the underlying ListModel once only. * In other words: the SelectionInList doesn't register * its ListDataListener multiple times. */ private void testSingleListDataListener(ValueModel listHolder) { new IndirectListModel(listHolder); ArrayListModel listModel1 = new ArrayListModel(); LinkedListModel listModel2 = new LinkedListModel(); listHolder.setValue(listModel1); assertEquals("SelectionInList registered its ListDataListener.", 1, listModel1.getListDataListeners().length); listHolder.setValue(listModel1); assertEquals("SelectionInList reregistered its ListDataListener.", 1, listModel1.getListDataListeners().length); listHolder.setValue(listModel2); assertEquals("SelectionInList deregistered its ListDataListener.", 0, listModel1.getListDataListeners().length); assertEquals("SelectionInList registered its ListDataListener.", 1, listModel2.getListDataListeners().length); } /** * Checks and verifies for a bunch of ListModel instances, * whether the ListDataListener has been reregistered properly. */ public void testReregisterListDataListener() { ObservableList empty1 = new ArrayListModel(); ObservableList empty2 = new ArrayListModel(); testReregistersListDataListener(empty1, empty2); ObservableList empty3 = new LinkedListModel(); ObservableList empty4 = new LinkedListModel(); testReregistersListDataListener(empty3, empty4); ObservableList array1 = new ArrayListModel(); ObservableList array2 = new ArrayListModel(); array1.add(Boolean.TRUE); array2.add(Boolean.TRUE); testReregistersListDataListener(array1, array2); ObservableList linked1 = new LinkedListModel(); ObservableList linked2 = new LinkedListModel(); linked1.add(Boolean.TRUE); linked2.add(Boolean.TRUE); testReregistersListDataListener(linked1, linked2); } /** * Checks and verifies whether the ListDataListener has been * reregistered properly. This will fail if the change support * fails to fire a change event when the instance changes.

* * Creates a SelectionInList on list1, then changes it to list2, * modifies both lists, and finally checks whether the SelectionInList * has fired the correct events. */ private void testReregistersListDataListener( ObservableList list1, ObservableList list2) { ListDataReport listDataReport1 = new ListDataReport(); ListDataReport listDataReport2 = new ListDataReport(); ListDataReport listDataReportSel = new ListDataReport(); IndirectListModel lmh = new IndirectListModel((ListModel) list1); // Change the list model. // Changes on list1 shall not affect the SelectionInList. // Changes in list2 shall be the same as for the SelectionInList. lmh.setListModel(list2); list1.addListDataListener(listDataReport1); list2.addListDataListener(listDataReport2); lmh.addListDataListener(listDataReportSel); // Modify both list models. list1.add("one1"); list1.add("two1"); list1.add("three1"); list1.add("four1"); list1.remove(1); list1.remove(0); list1.set(0, "newOne1"); list1.set(1, "newTwo1"); assertEquals("Events counted for list model 1", 8, listDataReport1.eventCount()); assertEquals("No events counted for list model 2", 0, listDataReport2.eventCount()); assertEquals("No events counted for the SelectionInList", 0, listDataReportSel.eventCount()); list2.add("one2"); list2.add("two2"); list2.add("three2"); list2.remove(1); list2.set(0, "newOne2"); assertEquals("Events counted for list model 2", 5, listDataReport2.eventCount()); assertEquals("Events counted for the SelectionInList", 5, listDataReportSel.eventCount()); // Compare the event lists. assertEquals("Events for list2 and SelectionInList differ.", listDataReport2, listDataReportSel); } // List Change Events on List Operations ********************************** /** * Tests the ListDataEvents fired during list changes. * The transitions are {} -> {} -> {a, b} -> {b, c} -> {a, b, c} -> {b, c} -> {}. */ public void testListDataEventsOnDirectListChange() { List list1 = Collections.emptyList(); List list2 = Collections.emptyList(); List list3 = Arrays.asList("a", "b"); List list4 = Arrays.asList("b", "c"); List list5 = Arrays.asList("a", "b", "c"); List list6 = Collections.emptyList(); IndirectListModel listHolder = new IndirectListModel(list1); ListDataReport report = new ListDataReport(); listHolder.addListDataListener(report); listHolder.setList(list2); assertEquals("The transition {} -> {} fires no ListDataEvent.", 0, report.eventCount()); report.clearEventList(); listHolder.setList(list3); assertEquals("The transition {} -> {a, b} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {} -> {a, b} fires an add event with interval[0, 1].", ListDataEvent.INTERVAL_ADDED, 0, 1, report.lastEvent()); report.clearEventList(); listHolder.setList(list4); assertEquals("The transition {a, b} -> {b, c} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a, b} -> {b, c} fires a contents changed event with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); listHolder.setList(list5); assertEquals("The transition {b, c} -> {a, b, c} fires two events.", 2, report.eventCount()); assertEvent("The transition {b, c} -> {a, b, c} fires an add event with interval[2, 2].", ListDataEvent.INTERVAL_ADDED, 2, 2, report.previousEvent()); assertEvent("The transition {b, c} -> {a, b, c} fires a contents changed with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); listHolder.setList(list4); assertEquals("The transition {a, b, c} -> {b, c} fires two events.", 2, report.eventCount()); assertEvent("The transition {a, b, c} -> {b, c} fires a remove event with interval[2, 2].", ListDataEvent.INTERVAL_REMOVED, 2, 2, report.previousEvent()); assertEvent("The transition {a, b, c} -> {b, c} fires a contents changed with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); listHolder.setList(list6); assertEquals("The transition {b, c} -> {} fires one event.", 1, report.eventCount()); assertEvent("The transition {b, c} -> {} fires a remove event with interval[0, 1].", ListDataEvent.INTERVAL_REMOVED, 0, 1, report.lastEvent()); } /** * Tests that ListDataEvents fired during list changes * provide size information that are consistent with the * size of the list model. * The transitions are {} -> {} -> {a, b} -> {b, c} -> {a, b, c} -> {b, c} -> {}. */ public void testListDataEventsRetainSizeConstraints() { List list1 = Collections.emptyList(); List list2 = Collections.emptyList(); List list3 = Arrays.asList(new String[]{"a", "b"}); List list4 = Arrays.asList(new String[]{"b", "c"}); List list5 = Arrays.asList(new String[]{"a", "b", "c"}); List list6 = Collections.emptyList(); IndirectListModel listHolder = new IndirectListModel(list1); listHolder.addListDataListener( new ListSizeConstraintChecker(listHolder.getSize())); listHolder.setList(list2); listHolder.setList(list3); listHolder.setList(list4); listHolder.setList(list5); listHolder.setList(list4); listHolder.setList(list6); } /** * Tests the ListDataEvents fired during list changes. * The transitions are {} -> {a} -> {b} -> {} on the same list. */ public void testListDataEventsOnIndirectListChange() { String element1 = "a"; String element2 = "b"; List list = new ArrayList(); ValueHolder listInModel = new ValueHolder(list, true); IndirectListModel listHolder = new IndirectListModel(listInModel); ListDataReport report = new ListDataReport(); listHolder.addListDataListener(report); list.add(element1); listInModel.fireValueChange(null, list); assertEquals("The transition {} -> {a} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {} -> {a} fires an add event with interval[0, 0].", ListDataEvent.INTERVAL_ADDED, 0, 0, report.lastEvent()); report.clearEventList(); list.remove(element1); list.add(element2); listInModel.fireValueChange(null, list); assertEquals("The transition {a} -> {b} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a} -> {b} fires a contents changed event with interval[0, 0].", ListDataEvent.CONTENTS_CHANGED, 0, 0, report.lastEvent()); report.clearEventList(); list.remove(element2); listInModel.fireValueChange(null, list); assertEquals("The transition {b} -> {} fires one event.", 1, report.eventCount()); assertEvent("The transition {b} -> {} fires a remove event with interval[0, 0].", ListDataEvent.INTERVAL_REMOVED, 0, 0, report.lastEvent()); } /** * Tests the ListDataEvents fired in list changes after elements * have been added. Shall detect bugs where the IndirectListModel's internal * listSize is unsynchronized with the list. */ public void testListDataEventsOnListAdd() { String element1 = "a"; String element2 = "b"; List list = new ArrayList(); ValueHolder listInModel = new ValueHolder(list, true); IndirectListModel listHolder = new IndirectListModel(listInModel); ListDataReport report = new ListDataReport(); listHolder.addListDataListener(report); list.add(element1); listHolder.fireIntervalAdded(0, 0); assertEquals("The transition {} -> {a} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {} -> {a} fires an add event with interval[0, 0].", ListDataEvent.INTERVAL_ADDED, 0, 0, report.lastEvent()); report.clearEventList(); list.remove(element1); list.add(element2); listInModel.fireValueChange(null, list); assertEquals("The transition {a} -> {b} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a} -> {b} fires a contents changed event with interval[0, 0].", ListDataEvent.CONTENTS_CHANGED, 0, 0, report.lastEvent()); report.clearEventList(); } /** * Tests the ListDataEvents fired in list changes after elements * have been removed. Shall detect bugs where the IndirectListModel's internal * listSize is unsynchronized with the list. */ public void testListDataEventsOnListRemove() { String element1 = "a"; String element2 = "b"; List list = new ArrayList(); list.add(element1); ValueHolder listInModel = new ValueHolder(list, true); IndirectListModel listHolder = new IndirectListModel(listInModel); ListDataReport report = new ListDataReport(); listHolder.addListDataListener(report); list.remove(0); listHolder.fireIntervalRemoved(0, 0); assertEquals("The transition {a} -> {} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a} -> {} fires a remove event with interval[0, 0].", ListDataEvent.INTERVAL_REMOVED, 0, 0, report.lastEvent()); report.clearEventList(); list.add(element2); listInModel.fireValueChange(null, list); assertEquals("The transition {} -> {b} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a} -> {b} fires an add event with interval[0, 0].", ListDataEvent.INTERVAL_ADDED, 0, 0, report.lastEvent()); report.clearEventList(); } // List Change Events on ListModel Operations ***************************** /** * Tests the ListDataEvents fired during ListModel changes. * The transitions are {} -> {} -> {a, b} -> {b, c} -> {a, b, c} -> {b, c} -> {}. */ public void testListDataEventsOnDirectListModelChange() { ListModel list1 = createListModel(); ListModel list2 = createListModel(); ListModel list3 = createListModel("a", "b"); ListModel list4 = createListModel("b", "c"); ListModel list5 = createListModel("a", "b", "c"); ListModel list6 = createListModel(); IndirectListModel listModelHolder = new IndirectListModel(list1); ListDataReport report = new ListDataReport(); listModelHolder.addListDataListener(report); listModelHolder.setListModel(list2); assertEquals("The transition {} -> {} fires no ListDataEvent.", 0, report.eventCount()); report.clearEventList(); listModelHolder.setListModel(list3); assertEquals("The transition {} -> {a, b} fires 1 add event.", 1, report.eventCount()); assertEvent("The transition {} -> {a, b} fires an add event with interval[0, 1].", ListDataEvent.INTERVAL_ADDED, 0, 1, report.lastEvent()); report.clearEventList(); listModelHolder.setListModel(list4); assertEquals("The transition {a, b} -> {b, c} fires 1 add event.", 1, report.eventCount()); assertEvent("The transition {a, b} -> {b, c} fires an add event with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); listModelHolder.setListModel(list5); assertEquals("The transition {b, c} -> {a, b, c} fires two events.", 2, report.eventCount()); assertEvent("The transition {b, c} -> {a, b, c} fires an add event with interval[2, 2].", ListDataEvent.INTERVAL_ADDED, 2, 2, report.previousEvent()); assertEvent("The transition {b, c} -> {a, b, c} fires a contents changed with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); listModelHolder.setListModel(list4); assertEquals("The transition {a, b, c} -> {b, c} fires two events.", 2, report.eventCount()); assertEvent("The transition {a, b, c} -> {b, c} fires a remove event with interval[2, 2].", ListDataEvent.INTERVAL_REMOVED, 2, 2, report.previousEvent()); assertEvent("The transition {a, b, c} -> {b, c} fires a contents changed with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); listModelHolder.setListModel(list6); assertEquals("The transition {b, c} -> {} fires one event.", 1, report.eventCount()); assertEvent("The transition {b, c} -> {} fires a remove event with interval[0, 1].", ListDataEvent.INTERVAL_REMOVED, 0, 1, report.lastEvent()); } /** * Tests that ListDataEvents fired during list changes * provide size information that are consistent with the * size of the list model. * The transitions are {} -> {} -> {a, b} -> {b, c} -> {a, b, c} -> {b, c} -> {}. */ public void testListDataEventsRetainListModelSizeConstraints() { ListModel list1 = createListModel(); ListModel list2 = createListModel(); ListModel list3 = createListModel("a", "b"); ListModel list4 = createListModel("b", "c"); ListModel list5 = createListModel("a", "b", "c"); ListModel list6 = createListModel(); IndirectListModel listModelHolder = new IndirectListModel(list1); listModelHolder.addListDataListener( new ListSizeConstraintChecker(listModelHolder.getSize())); listModelHolder.setListModel(list2); listModelHolder.setListModel(list3); listModelHolder.setListModel(list4); listModelHolder.setListModel(list5); listModelHolder.setListModel(list4); listModelHolder.setListModel(list6); } /** * Tests the ListDataEvents fired during list changes. * The transitions are {} -> {a} -> {b} -> {} on the same list. */ public void testListDataEventsOnIndirectListModelChange() { String element1 = "a"; String element2 = "b"; DeadListModel list = new DeadListModel(); ValueHolder listModelInModel = new ValueHolder(list, true); IndirectListModel listModelHolder = new IndirectListModel(listModelInModel); ListDataReport report = new ListDataReport(); listModelHolder.addListDataListener(report); list.add(element1); listModelInModel.fireValueChange(null, list); assertEquals("The transition {} -> {a} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {} -> {a} fires an add event with interval[0, 0].", ListDataEvent.INTERVAL_ADDED, 0, 0, report.lastEvent()); report.clearEventList(); list.remove(element1); list.add(element2); listModelInModel.fireValueChange(null, list); assertEquals("The transition {a} -> {b} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a} -> {b} fires a contents changed event with interval[0, 0].", ListDataEvent.CONTENTS_CHANGED, 0, 0, report.lastEvent()); report.clearEventList(); list.remove(element2); listModelInModel.fireValueChange(null, list); assertEquals("The transition {b} -> {} fires one event.", 1, report.eventCount()); assertEvent("The transition {b} -> {} fires a remove event with interval[0, 0].", ListDataEvent.INTERVAL_REMOVED, 0, 0, report.lastEvent()); } /** * Tests the ListDataEvents fired in list changes after elements * have been added. Shall detect bugs where the ListHolder's internal * listSize is unsynchronized with the list. */ public void testListDataEventsOnListModelAdd() { String element1 = "a"; String element2 = "b"; List list1 = new ArrayListModel(); List list2 = new ArrayListModel(); list2.add(element2); ValueHolder listInModel = new ValueHolder(list1, true); IndirectListModel listModelHolder = new IndirectListModel(listInModel); ListDataReport report = new ListDataReport(); listModelHolder.addListDataListener(report); list1.add(element1); assertEquals("The transition {} -> {a} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {} -> {a} fires an add event with interval[0, 0].", ListDataEvent.INTERVAL_ADDED, 0, 0, report.lastEvent()); report.clearEventList(); listInModel.setValue(list2); assertEquals("The transition {a} -> {b} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a} -> {b} fires a contents changed event with interval[0, 0].", ListDataEvent.CONTENTS_CHANGED, 0, 0, report.lastEvent()); report.clearEventList(); } /** * Tests the ListDataEvents fired in list changes after elements * have been removed. Shall detect bugs where the ListHolder's internal * listSize is unsynchronized with the list. */ public void testListDataEventsOnListModelRemove() { String element1 = "a"; String element2 = "b"; List list1 = new ArrayListModel(); list1.add(element1); List list2 = new ArrayListModel(); list2.add(element2); ValueHolder listInModel = new ValueHolder(list1, true); IndirectListModel listHolder = new IndirectListModel(listInModel); ListDataReport report = new ListDataReport(); listHolder.addListDataListener(report); list1.remove(0); assertEquals("The transition {a} -> {} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {a} -> {} fires a remove event with interval[0, 0].", ListDataEvent.INTERVAL_REMOVED, 0, 0, report.lastEvent()); report.clearEventList(); listInModel.setValue(list2); assertEquals("The transition {} -> {b} fires 1 event.", 1, report.eventCount()); assertEvent("The transition {} -> {b} fires an add event with interval[0, 0].", ListDataEvent.INTERVAL_ADDED, 0, 0, report.lastEvent()); report.clearEventList(); } private void assertEvent(String description, int eventType, int index0, int index1, ListDataEvent event) { assertEquals("Type: " + description, eventType, event.getType()); assertEquals("Index0: " + description, index0, event.getIndex0()); assertEquals("Index1: " + description, index1, event.getIndex1()); } // Helper Code ************************************************************ private DefaultListModel createListModel(Object... elements) { DefaultListModel model = new DefaultListModel(); for (Object element : elements) { model.addElement(element); } return model; } /** * A ListModel that wraps a List and doesn't fire ListDataEvents * on List content changes. */ private static final class DeadListModel implements ListModel { private final List list; DeadListModel() { this.list = new ArrayList(); } public int getSize() { return list.size(); } public String getElementAt(int index) { return list.get(index); } public void add(String element) { list.add(element); } public void remove(String element) { list.remove(element); } public void addListDataListener(ListDataListener l) { // Ignore; this ListModel doesn't fire events. } public void removeListDataListener(ListDataListener l) { // Ignore; this ListModel doesn't fire events. } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/AllBindingTests.java0000644000175000017500000000566611374522114027474 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import org.junit.runner.RunWith; import org.junit.runners.Suite; /** * A test suite for all tests related to the JGoodies Binding framework. * * @author Karsten Lentzsch * @version $Revision: 1.45 $ */ @RunWith(Suite.class) @Suite.SuiteClasses({ AbstractConverterTest.class, AbstractTableAdapterTest.class, BasicComponentFactoryTest.class, BeanAdapterTest.class, BeanUtilsTest.class, BindingsTest.class, BoundedRangeAdapterTest.class, BufferedValueModelTest.class, CombinedTest.class, ComboBoxAdapterTest.class, ConverterFactoryTest.class, ExtendedPropertyChangeSupportTest.class, IndirectPropertyChangeSupportTest.class, IndirectListModelTest.class, PreferencesAdapterTest.class, PresentationModelTest.class, PropertyAdapterTest.class, PropertyConnectorTest.class, PropertyExceptionTest.class, RadioButtonAdapterTest.class, ReflectionTest.class, SelectionInListTest.class, SingleListSelectionAdapterTest.class, SpinnerAdapterFactoryTest.class, TextComponentConnectorTest.class, ToggleButtonAdapterTest.class, TriggerTest.class, ValueChangeTest.class, ValueHolderTest.class }) public final class AllBindingTests { // Just a suite definition class. } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/BeanUtilsTest.java0000644000175000017500000001104111374522114027154 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.beans.PropertyVetoException; import junit.framework.TestCase; import com.jgoodies.binding.beans.BeanUtils; import com.jgoodies.binding.tests.beans.BeanClasses; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.beans.VetoableChangeRejector; /** * A test case for class {@link BeanUtils}. * * @author Karsten Lentzsch * @version $Revision: 1.17 $ */ public final class BeanUtilsTest extends TestCase { /** * Checks that #supportsBoundProperties detects observable classes. */ public void testDetectObservableClasses() { for (Class beanClass : BeanClasses.getObservableClasses()) { assertTrue( "Could not detect that the class supports bound properties.", BeanUtils.supportsBoundProperties(beanClass)); } } /** * Checks that #supportsBoundProperties rejects unobservable classes. */ public void testRejectUnobservableClasses() { for (Class beanClass : BeanClasses.getUnobservableClasses()) { assertFalse( "Failed to reject a class that supports no bound properties.", BeanUtils.supportsBoundProperties(beanClass)); } } public void testWriteConstrainedProperty() { TestBean bean = new TestBean(); try { bean.setConstrainedProperty("value1"); } catch (PropertyVetoException e1) { fail("Couldn't set the valid value1."); } assertEquals("Bean has the initial value1.", bean.getConstrainedProperty(), "value1"); PropertyDescriptor descriptor = null; try { descriptor = BeanUtils.getPropertyDescriptor(bean.getClass(), "constrainedProperty"); try { BeanUtils.setValue(bean, descriptor, "value2"); } catch (PropertyVetoException e) { fail("No PropertyVetoException shall be thrown if there's no VetoableChangeListener."); } assertEquals("Bean now has the value2.", bean.getConstrainedProperty(), "value2"); bean.addVetoableChangeListener(new VetoableChangeRejector()); try { BeanUtils.setValue(bean, descriptor, "value3"); fail("Setting a value that will be vetoed must throw an exception."); } catch (PropertyVetoException e) { // The expected behavior. } assertEquals("Bean still has the value2.", bean.getConstrainedProperty(), "value2"); } catch (IntrospectionException e) { fail("Couldn't look up the descriptor for the constrained property."); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/RadioButtonAdapterTest.java0000644000175000017500000002667211374522114031041 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import junit.framework.TestCase; import com.jgoodies.binding.adapter.RadioButtonAdapter; import com.jgoodies.binding.beans.PropertyAdapter; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.event.ChangeReport; import com.jgoodies.binding.tests.event.ItemChangeReport; import com.jgoodies.binding.tests.value.RejectingValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * A test case for class {@link RadioButtonAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.18 $ */ public final class RadioButtonAdapterTest extends TestCase { // Parameter Tests ****************************************************** public void testConstructorRejectsNullSubject() { try { new RadioButtonAdapter(null, "choice"); fail("RadioButtonAdapter(ValueModel, Object) failed to reject a null subject."); } catch (NullPointerException ex) { // The expected behavior } } public void testRejectsButtonGroup() { ButtonModel model = new RadioButtonAdapter(new ValueHolder(), "wow"); ButtonGroup group = new ButtonGroup(); try { model.setGroup(group); fail("RadioButtonAdapter.setGroup(ButtonGroup) must reject all ButtonGroups."); } catch (UnsupportedOperationException ex) { // The expected behavior } } // Basic Adapter Features ************************************************* public void testAdaptsReadWriteBooleanProperty() { TestBean bean = new TestBean(); bean.setReadWriteBooleanProperty(true); ValueModel subject = new PropertyAdapter(bean, "readWriteBooleanProperty", true); RadioButtonAdapter adapter1 = new RadioButtonAdapter(subject, Boolean.TRUE); RadioButtonAdapter adapter2 = new RadioButtonAdapter(subject, Boolean.FALSE); // Reading assertTrue ("Adapter1 is selected.", adapter1.isSelected()); assertFalse("Adapter2 is deselected.", adapter2.isSelected()); bean.setReadWriteBooleanProperty(false); assertFalse("Adapter1 is deselected.", adapter1.isSelected()); assertTrue ("Adapter2 is selected.", adapter2.isSelected()); bean.setReadWriteBooleanProperty(true); assertTrue ("Adapter1 is selected again.", adapter1.isSelected()); assertFalse("Adapter2 is deselected again.", adapter2.isSelected()); // Writing adapter2.setSelected(true); assertFalse("Adapted property is false.", bean.isReadWriteBooleanProperty()); adapter1.setSelected(true); assertTrue("Adapted property is true.", bean.isReadWriteBooleanProperty()); } public void testReadWriteOperations() { testReadWriteOperations(null, "one", "two"); testReadWriteOperations(null, null, "two"); testReadWriteOperations(null, "one", null); testReadWriteOperations("one", "one", "two"); testReadWriteOperations("one", null, "two"); testReadWriteOperations("one", "one", null); testReadWriteOperations("two", "one", "two"); testReadWriteOperations("two", null, "two"); testReadWriteOperations("two", "one", null); } private void testReadWriteOperations( Object initialValue, Object value1, Object value2) { ValueModel subject = new ValueHolder(initialValue); RadioButtonAdapter adapter1 = new RadioButtonAdapter(subject, value1); RadioButtonAdapter adapter2 = new RadioButtonAdapter(subject, value2); // Reading assertEquals("Adapter1 selection reflects the initial subject value.", Objects.equals(initialValue, value1), adapter1.isSelected()); assertEquals("Adapter2 selection reflects the initial subject value.", Objects.equals(initialValue, value2), adapter2.isSelected()); subject.setValue(value1); assertTrue ("Adapter1 is selected.", adapter1.isSelected()); assertFalse("Adapter2 is deselected.", adapter2.isSelected()); subject.setValue(value2); assertFalse("Adapter1 is deselected again.", adapter1.isSelected()); assertTrue ("Adapter2 is selected again.", adapter2.isSelected()); // Writing adapter1.setSelected(true); assertEquals("The subject value is value1.", value1, subject.getValue()); adapter1.setSelected(true); assertEquals("The subject value is still value1.", value1, subject.getValue()); adapter2.setSelected(false); assertEquals("Deselecting an adapter doesn't modify the subject value.", value1, subject.getValue()); adapter2.setSelected(true); assertEquals("The subject value is value2.", value2, subject.getValue()); adapter2.setSelected(true); assertEquals("The subject value is still value2.", value2, subject.getValue()); } // Change and Item Events ************************************************* public void testFiresChangeAndItemEvents() { Object value1 = "value1"; Object value2 = "value2"; TestBean bean = new TestBean(); bean.setReadWriteObjectProperty(value1); ValueModel subject = new PropertyAdapter(bean, "readWriteObjectProperty", true); RadioButtonAdapter adapter1 = new RadioButtonAdapter(subject, value1); RadioButtonAdapter adapter2 = new RadioButtonAdapter(subject, value2); ChangeReport changeReport = new ChangeReport(); ItemChangeReport itemChangeReport = new ItemChangeReport(); adapter1.addChangeListener(changeReport); adapter1.addItemListener(itemChangeReport); bean.setReadWriteObjectProperty(value2); assertEquals("Deselecting the property fires a ChangeEvent.", 1, changeReport.eventCount()); assertEquals("Deselecting the property fires an ItemChangeEvent.", 1, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates deselected.", itemChangeReport.isLastStateChangeDeselected()); bean.setReadWriteObjectProperty(value1); assertEquals("Selecting the property fires another ChangeEvent.", 2, changeReport.eventCount()); assertEquals("Selecting the property fires another ItemChangeEvent.", 2, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates selected.", itemChangeReport.isLastStateChangeSelected()); adapter1.setSelected(false); assertEquals("Deselecting the adapter fires no ChangeEvent.", 2, changeReport.eventCount()); assertEquals("Deselecting the adapter fires no ItemChangeEvent.", 2, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates selected.", itemChangeReport.isLastStateChangeSelected()); adapter2.setSelected(true); assertEquals("Selecting a grouped adapter fires a single ChangeEvent.", 3, changeReport.eventCount()); assertEquals("Selecting a grouped adapter fires a single ItemChangeEvent.", 3, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates deselected.", itemChangeReport.isLastStateChangeDeselected()); adapter1.setSelected(true); assertEquals("Selecting the adapter fires another ChangeEvent.", 4, changeReport.eventCount()); assertEquals("Selecting the adapter fires another ItemChangeEvent.", 4, itemChangeReport.eventCount()); assertTrue("The last ItemChangeEvent indicates selected.", itemChangeReport.isLastStateChangeSelected()); } // Read-Only Model ******************************************************** public void testAdaptsReadOnlyObjectProperty() { Object value1 = "value1"; Object value2 = "value2"; TestBean bean = new TestBean(); bean.fireChangeOnReadOnlyObjectProperty(value1); ValueModel subject = new PropertyAdapter(bean, "readOnlyObjectProperty", true); RadioButtonAdapter adapter = new RadioButtonAdapter(subject, value1); assertTrue("Adapter is selected.", adapter.isSelected()); bean.fireChangeOnReadOnlyObjectProperty(value2); assertFalse("Adapter is deselected.", adapter.isSelected()); bean.fireChangeOnReadOnlyObjectProperty(value1); assertTrue("Adapter is selected again.", adapter.isSelected()); } // Re-Synchronizes if the Subject Change is Rejected ********************** public void testResynchronizesAfterRejectedSubjectChange() { ValueModel selectionHolder = new ValueHolder(Boolean.FALSE); ValueModel rejectingSelectionHolder = new RejectingValueModel(selectionHolder); RadioButtonAdapter adapter = new RadioButtonAdapter(rejectingSelectionHolder, Boolean.TRUE); assertFalse("Adapter is deselected.", adapter.isSelected()); adapter.setSelected(true); assertFalse("Adapter is still deselected.", adapter.isSelected()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/BeanAdapterTest.java0000644000175000017500000012724111374522114027446 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import junit.framework.TestCase; import com.jgoodies.binding.beans.*; import com.jgoodies.binding.tests.beans.*; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.tests.value.ValueHolderWithNewValueNull; import com.jgoodies.binding.tests.value.ValueHolderWithOldAndNewValueNull; import com.jgoodies.binding.tests.value.ValueHolderWithOldValueNull; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link com.jgoodies.binding.beans.BeanAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.31 $ */ public final class BeanAdapterTest extends TestCase { private TestBean model1; private TestBean model2; /** * @throws Exception in case of an unexpected problem */ @Override protected void setUp() throws Exception { super.setUp(); model1 = new TestBean(); model2 = new TestBean(); } /** * @throws Exception in case of an unexpected problem */ @Override protected void tearDown() throws Exception { super.tearDown(); model1 = null; model2 = null; } // Constructor Tests ****************************************************** /** * Verifies that we can adapt observable and non-observable objects * if we do not observe changes. */ public void testConstructorsAcceptAllBeansWhenNotObserving() { for (Object bean : BeanClasses.getBeans()) { try { new BeanAdapter(bean, false); new BeanAdapter(new ValueHolder(bean, true), false); } catch (PropertyUnboundException ex) { fail("Constructor must not try to observe with observeChanges == false"); } } } /** * Verifies that we can adapt and observe observables. */ public void testConstructorsAcceptToObserveObservables() { for (Object bean : BeanClasses.getObservableBeans()) { try { new BeanAdapter(bean, true); new BeanAdapter(new ValueHolder(bean, true), true); } catch (PropertyUnboundException ex) { fail("Constructor failed to accept to observe an observable." + "\nbean class=" + bean.getClass() + "\nexception=" + ex); } } } public void testConstructorRejectsNonIdentityCheckingBeanChannel() { try { new BeanAdapter(new ValueHolder(null)); fail("Constructor must reject bean channel that has the identity check feature disabled."); } catch (IllegalArgumentException e) { // The expected behavior } } /** * Verifies that we cannot observe non-observables. */ public void testConstructorsRejectToObserveObservables() { for (Object bean : BeanClasses.getUnobservableBeans()) { try { new BeanAdapter(bean, true); fail("Constructor must reject to observe non-observables."); } catch (PropertyUnboundException ex) { // The expected behavior } try { new BeanAdapter(new ValueHolder(bean, true), true); fail("Constructor must reject to observe non-observables."); } catch (PropertyUnboundException ex) { // The expected behavior } } } /** * Verifies that we cannot observe non-observables. */ public void testConstructorsAcceptsToObserveObjectsThatSupportBoundProperties() { for (Object bean : BeanClasses.getBeans()) { try { boolean supportsBoundProperties = BeanUtils.supportsBoundProperties(bean.getClass()); new BeanAdapter(bean, supportsBoundProperties); new BeanAdapter(new ValueHolder(bean, true), supportsBoundProperties); } catch (PropertyUnboundException ex) { fail("Constructor failed to accept to observe an observable." + "\nbean class=" + bean.getClass() + "\nexception=" + ex); } } } public void testConstructorAcceptsNullBean() { try { new BeanAdapter((Object) null, true); } catch (NullPointerException ex) { ex.printStackTrace(); fail("Constructor failed to accept a null bean." + "\nexception=" + ex); } } public void testAcceptsMultipleReleases() { BeanAdapter adapter = new BeanAdapter(model1); adapter.release(); adapter.release(); } // Null As Property Name ************************************************** public void testRejectNullPropertyName() { testRejectNullPropertyName(null); testRejectNullPropertyName(new TestBean()); } private void testRejectNullPropertyName(Object bean) { BeanAdapter adapter = new BeanAdapter(bean); try { adapter.getValue(null); fail("#getValue(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { adapter.setValue(null, null); fail("#setValue(null, Object) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { adapter.setVetoableValue(null, null); fail("#setVetoableValue(null, Object) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } catch (PropertyVetoException e) { fail("An NPE should be thrown."); } try { adapter.getValueModel(null); fail("#getValueModel(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { adapter.getValueModel(null, "readA", "writeA"); fail("#getValueModel(null, String, String) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } } // Testing Property Access ************************************************ /** */ public void testReadWriteProperties() { TestBean bean = new TestBean(); bean.setReadWriteObjectProperty("initialValue"); bean.setReadWriteBooleanProperty(false); bean.setReadWriteIntProperty(42); ValueModel objectAdapter = new BeanAdapter(bean).getValueModel("readWriteObjectProperty"); ValueModel booleanAdapter = new BeanAdapter(bean).getValueModel("readWriteBooleanProperty"); ValueModel intAdapter = new BeanAdapter(bean).getValueModel("readWriteIntProperty"); ValueModel integerAdapter = new BeanAdapter(bean).getValueModel("readWriteIntegerProperty"); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.getReadWriteObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.isReadWriteBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.getReadWriteIntProperty())); assertEquals(integerAdapter.getValue(), bean.getReadWriteIntegerProperty()); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(43); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); integerAdapter.setValue(integerValue); assertEquals(objectValue, bean.getReadWriteObjectProperty()); assertEquals(booleanValue, Boolean.valueOf(bean.isReadWriteBooleanProperty())); assertEquals(integerValue, new Integer(bean.getReadWriteIntProperty())); assertEquals(integerValue, bean.getReadWriteIntegerProperty()); } public void testReadWriteCustomProperties() { CustomAccessBean bean = new CustomAccessBean(); bean.writeRWObjectProperty("initialValue"); bean.writeRWBooleanProperty(false); bean.writeRWIntProperty(42); ValueModel objectAdapter = new BeanAdapter(bean).getValueModel("readWriteObjectProperty", "readRWObjectProperty", "writeRWObjectProperty"); ValueModel booleanAdapter = new BeanAdapter(bean).getValueModel("readWriteBooleanProperty", "readRWBooleanProperty", "writeRWBooleanProperty"); ValueModel intAdapter = new BeanAdapter(bean).getValueModel("readWriteIntProperty", "readRWIntProperty", "writeRWIntProperty"); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.readRWObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.readRWBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.readRWIntProperty())); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(43); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); assertEquals(objectValue, bean.readRWObjectProperty()); assertEquals(booleanValue, Boolean.valueOf(bean.readRWBooleanProperty())); assertEquals(integerValue, new Integer(bean.readRWIntProperty())); } public void testReadOnlyProperties() { TestBean bean = new TestBean(); bean.readOnlyObjectProperty = "testString"; bean.readOnlyBooleanProperty = true; bean.readOnlyIntProperty = 42; ValueModel objectAdapter = new BeanAdapter(bean).getValueModel("readOnlyObjectProperty"); ValueModel booleanAdapter = new BeanAdapter(bean).getValueModel("readOnlyBooleanProperty"); ValueModel intAdapter = new BeanAdapter(bean).getValueModel("readOnlyIntProperty"); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.getReadOnlyObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.isReadOnlyBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.getReadOnlyIntProperty())); try { objectAdapter.setValue("some"); fail("Adapter must reject writing of read-only properties."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } public void testReadOnlyCustomProperties() { CustomAccessBean bean = new CustomAccessBean(); bean.readOnlyObjectProperty = "testString"; bean.readOnlyBooleanProperty = true; bean.readOnlyIntProperty = 42; ValueModel objectAdapter = new BeanAdapter(bean).getValueModel("readOnlyObjectProperty", "readROObjectProperty", null); ValueModel booleanAdapter = new BeanAdapter(bean).getValueModel("readOnlyBooleanProperty", "readROBooleanProperty", null); ValueModel intAdapter = new BeanAdapter(bean).getValueModel("readOnlyIntProperty", "readROIntProperty", null); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.readROObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.readROBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.readROIntProperty())); try { objectAdapter.setValue("some"); fail("Adapter must reject writing of read-only properties."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } /** */ public void testWriteOnlyProperties() { TestBean bean = new TestBean(); ValueModel objectAdapter = new BeanAdapter(bean).getValueModel("writeOnlyObjectProperty"); ValueModel booleanAdapter = new BeanAdapter(bean).getValueModel("writeOnlyBooleanProperty"); ValueModel intAdapter = new BeanAdapter(bean).getValueModel("writeOnlyIntProperty"); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(42); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); assertEquals(objectValue, bean.writeOnlyObjectProperty); assertEquals(booleanValue, Boolean.valueOf(bean.writeOnlyBooleanProperty)); assertEquals(integerValue, new Integer(bean.writeOnlyIntProperty)); try { objectAdapter.getValue(); fail("Adapter must reject to read a write-only property."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } public void testWriteOnlyCustomProperties() { CustomAccessBean bean = new CustomAccessBean(); ValueModel objectAdapter = new BeanAdapter(bean).getValueModel("writeOnlyObjectProperty", null, "writeWOObjectProperty"); ValueModel booleanAdapter = new BeanAdapter(bean).getValueModel("writeOnlyBooleanProperty", null, "writeWOBooleanProperty"); ValueModel intAdapter = new BeanAdapter(bean).getValueModel("writeOnlyIntProperty", null, "writeWOIntProperty"); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(42); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); assertEquals(objectValue, bean.writeOnlyObjectProperty); assertEquals(booleanValue, Boolean.valueOf(bean.writeOnlyBooleanProperty)); assertEquals(integerValue, new Integer(bean.writeOnlyIntProperty)); try { objectAdapter.getValue(); fail("Adapter must reject to read a write-only property."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } /** * Checks the write access to a constrained property without veto. */ public void testWriteConstrainedPropertyWithoutVeto() { TestBean bean = new TestBean(); BeanAdapter.SimplePropertyAdapter adapter = new BeanAdapter(bean).getValueModel("constrainedProperty"); try { bean.setConstrainedProperty("value1"); } catch (PropertyVetoException e1) { fail("Unexpected veto for value1."); } assertEquals("Bean has the initial value1.", bean.getConstrainedProperty(), "value1"); adapter.setValue("value2a"); assertEquals("Bean now has the value2a.", bean.getConstrainedProperty(), "value2a"); try { adapter.setVetoableValue("value2b"); } catch (PropertyVetoException e) { fail("Unexpected veto for value2b."); } assertEquals("Bean now has the value2b.", bean.getConstrainedProperty(), "value2b"); } /** * Checks the write access to a constrained property with veto. */ public void testWriteConstrainedPropertyWithVeto() { TestBean bean = new TestBean(); PropertyAdapter adapter = new PropertyAdapter(bean, "constrainedProperty"); try { bean.setConstrainedProperty("value1"); } catch (PropertyVetoException e1) { fail("Unexpected veto for value1."); } assertEquals("Bean has the initial value1.", bean.getConstrainedProperty(), "value1"); // Writing with a veto bean.addVetoableChangeListener(new VetoableChangeRejector()); adapter.setValue("value2a"); assertEquals("Bean still has the value1.", bean.getConstrainedProperty(), "value1"); try { adapter.setVetoableValue("value2b"); fail("Couldn't set the valid value1"); } catch (PropertyVetoException e) { PropertyChangeEvent pce = e.getPropertyChangeEvent(); assertEquals("The veto's old value is value1.", pce.getOldValue(), "value1"); assertEquals("The veto's new value is value2b.", pce.getNewValue(), "value2b"); } assertEquals("After setting value2b, the bean still has the value1.", bean.getConstrainedProperty(), "value1"); } /** * Verifies that the reader and writer can be located in different * levels of a class hierarchy. */ public void testReaderWriterSplittedInHierarchy() { ReadWriteHierarchyBean bean = new ReadWriteHierarchyBean(); ValueModel adapter = new BeanAdapter(bean).getValueModel("property"); Object value1 = "Ewa"; String value2 = "Karsten"; adapter.setValue(value1); assertEquals(value1, bean.getProperty()); bean.setProperty(value2); assertEquals(value2, adapter.getValue()); } /** * Tests access to properties that are described by a BeanInfo class. */ public void testBeanInfoProperties() { CustomBean bean = new CustomBean(); ValueModel adapter = new BeanAdapter(bean).getValueModel("readWriteObjectProperty"); Object value1 = "Ewa"; String value2 = "Karsten"; adapter.setValue(value1); assertEquals(value1, bean.getReadWriteObjectProperty()); bean.setReadWriteObjectProperty(value2); assertEquals(value2, adapter.getValue()); try { new BeanAdapter(bean).getValueModel("readWriteIntProperty"); fail("Adapter must not find properties that " + "have been excluded by a custom BeanInfo."); } catch (PropertyNotFoundException e) { // The expected behavior } } public void testAbsentProperties() { TestBean bean = new TestBean(); try { new BeanAdapter(bean).getValueModel("absentObjectProperty"); fail("Adapter must reject an absent object property."); } catch (PropertyNotFoundException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new BeanAdapter(bean).getValueModel("absentBooleanProperty"); fail("Adapter must reject an absent boolean property."); } catch (PropertyNotFoundException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new BeanAdapter(bean).getValueModel("absentIntProperty"); fail("Adapter must reject an absent int property."); } catch (PropertyNotFoundException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } public void testIllegalPropertyAccess() { TestBean bean = new TestBean(); try { new BeanAdapter(bean).getValueModel("noAccess").getValue(); fail("Adapter must report read-access problems."); } catch (PropertyAccessException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new BeanAdapter(bean).getValueModel("noAccess").setValue("Fails"); fail("Adapter must report write-access problems."); } catch (PropertyAccessException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new BeanAdapter(bean).getValueModel("readWriteIntProperty").setValue(1967L); fail("Adapter must report IllegalArgumentExceptions."); } catch (PropertyAccessException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } // Testing Update Notifications ******************************************* public void testSetPropertySendsUpdates() { BeanAdapter adapter = new BeanAdapter(model1, true); String propertyName = "readWriteObjectProperty"; AbstractValueModel valueModel = adapter.getValueModel(propertyName); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addPropertyChangeListener("value", changeReport); model1.setReadWriteObjectProperty("Karsten"); assertEquals("First property change.", 1, changeReport.eventCount()); model1.setReadWriteObjectProperty("Ewa"); assertEquals("Second property change.", 2, changeReport.eventCount()); model1.setReadWriteObjectProperty(model1.getReadWriteObjectProperty()); assertEquals("Property change repeated.", 2, changeReport.eventCount()); } public void testSetValueModelSendsUpdates() { BeanAdapter adapter = new BeanAdapter(model1, true); String propertyName = "readWriteObjectProperty"; AbstractValueModel valueModel = adapter.getValueModel(propertyName); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addPropertyChangeListener("value", changeReport); valueModel.setValue("Johannes"); assertEquals("Value change.", 1, changeReport.eventCount()); valueModel.setValue(valueModel.getValue()); assertEquals("Value set again.", 1, changeReport.eventCount()); } public void testSetAdapterValueSendsUpdates() { BeanAdapter adapter = new BeanAdapter(model1, true); String propertyName = "readWriteObjectProperty"; AbstractValueModel valueModel = adapter.getValueModel(propertyName); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addPropertyChangeListener("value", changeReport); adapter.setValue(propertyName, "Johannes"); assertEquals("Value change.", 1, changeReport.eventCount()); adapter.setValue(propertyName, valueModel.getValue()); assertEquals("Value set again.", 1, changeReport.eventCount()); } public void testBeanChangeSendsUpdates() { BeanAdapter adapter = new BeanAdapter(model1, true); String propertyName = "readWriteObjectProperty"; model1.setReadWriteObjectProperty("initialValue"); AbstractValueModel valueModel = adapter.getValueModel(propertyName); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addPropertyChangeListener("value", changeReport); adapter.setBean(model1); assertEquals("First bean set.", 0, changeReport.eventCount()); adapter.setBean(new TestBean()); assertEquals("Second bean set.", 1, changeReport.eventCount()); } public void testMulticastAndNamedPropertyChangeEvents() { BeanAdapter adapter = new BeanAdapter(model1, true); String propertyName = "readWriteObjectProperty"; AbstractValueModel valueModel = adapter.getValueModel(propertyName); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addPropertyChangeListener("value", changeReport); model1.setReadWriteObjectProperty("Karsten"); assertEquals("Adapted property changed.", 1, changeReport.eventCount()); model1.setReadWriteBooleanProperty(false); assertEquals("Another property changed.", 1, changeReport.eventCount()); model1.setReadWriteObjectProperties(null, false, 3); assertEquals("Multiple properties changed.", 2, changeReport.eventCount()); } public void testMulticastFiresProperNewValue() { BeanAdapter adapter = new BeanAdapter(model1, true); String propertyName = "readWriteObjectProperty"; AbstractValueModel valueModel = adapter.getValueModel(propertyName); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addPropertyChangeListener("value", changeReport); Object theNewObjectValue = "The new value"; model1.setReadWriteObjectProperties(theNewObjectValue, false, 3); Object eventsNewValue = changeReport.lastNewValue(); assertEquals("Multicast fires proper new value .", theNewObjectValue, eventsNewValue); } // Misc ******************************************************************* /** * Checks that #setBean changes the bean and moves the * PropertyChangeListeners to the new bean. */ public void testSetBean() { Object value1_1 = "value1.1"; Object value1_2 = "value1.2"; Object value2_1 = "value2.1"; Object value2_2 = "value2.2"; Object value2_3 = "value2.3"; model1.setReadWriteObjectProperty(value1_1); model2.setReadWriteObjectProperty(value2_1); BeanAdapter adapter = new BeanAdapter(model1, true); String propertyName = "readWriteObjectProperty"; AbstractValueModel valueModel = adapter.getValueModel(propertyName); adapter.setBean(model2); assertSame( "Bean has not been changed.", adapter.getBean(), model2); assertSame( "Bean change does not answer the new beans's value.", valueModel.getValue(), value2_1); valueModel.setValue(value2_2); assertSame( "Bean change does not set the new bean's property.", model2.getReadWriteObjectProperty(), value2_2); model1.setReadWriteObjectProperty(value1_2); assertSame("Adapter listens to old bean after bean change.", valueModel.getValue(), value2_2); model2.setReadWriteObjectProperty(value2_3); assertSame("Adapter does not listen to new bean after bean change.", valueModel.getValue(), value2_3); } /** * Tests that we can change the bean if we adapt a write-only property. * Changing the bean normally calls the property's getter to request * the old value that is used in the fired PropertyChangeEvent. */ public void testSetBeanOnWriteOnlyProperty() { BeanAdapter adapter = new BeanAdapter(model1, true); adapter.getValueModel("writeOnlyObjectProperty"); adapter.setBean(model2); } /** * Tests that we can change the read/write state of the bean. */ public void testSetBeanChangesReadWriteState() { ReadWriteBean readWriteBean = new ReadWriteBean(); ReadOnlyBean readOnlyBean = new ReadOnlyBean(); WriteOnlyBean writeOnlyBean = new WriteOnlyBean(); // From/to readWriteBean to all other read/write states BeanAdapter adapter = new BeanAdapter(readWriteBean); adapter.setBean(null); adapter.setBean(readWriteBean); adapter.setBean(readOnlyBean); adapter.setBean(readWriteBean); adapter.setBean(writeOnlyBean); adapter.setBean(readWriteBean); // From/to writeOnlyBean to all other states adapter.setBean(writeOnlyBean); adapter.setBean(null); adapter.setBean(writeOnlyBean); adapter.setBean(readOnlyBean); adapter.setBean(writeOnlyBean); // From/to readOnlyBean to all other states adapter.setBean(readOnlyBean); adapter.setBean(null); adapter.setBean(readOnlyBean); } /** * Checks that bean changes are reported. */ public void testBeanIsBoundProperty() { BeanAdapter adapter = new BeanAdapter(model1); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("bean", changeReport); adapter.setBean(model2); assertEquals("Bean changed.", 1, changeReport.eventCount()); adapter.setBean(model2); assertEquals("Bean unchanged.", 1, changeReport.eventCount()); adapter.setBean(null); assertEquals("Bean set to null.", 2, changeReport.eventCount()); adapter.setBean(model1); assertEquals("Bean changed from null.", 3, changeReport.eventCount()); } public void testBeanChangeFiresThreeBeanEvents() { BeanAdapter adapter = new BeanAdapter((TestBean)null, true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener(changeReport); adapter.setBean(model1); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport.eventCount()); } public void testEqualBeanChangeFiresThreeBeanEvents() { Object bean1 = new EquityTestBean("bean"); Object bean2 = new EquityTestBean("bean"); assertEquals("The two test beans are equal.", bean1, bean2); assertNotSame("The two test beans are not the same.", bean1, bean2); BeanAdapter adapter1 = new BeanAdapter(bean1, true); PropertyChangeReport changeReport1 = new PropertyChangeReport(); adapter1.addPropertyChangeListener(changeReport1); adapter1.setBean(bean2); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport1.eventCount()); BeanAdapter adapter2 = new BeanAdapter(null, true); adapter2.setBean(bean1); PropertyChangeReport changeReport2 = new PropertyChangeReport(); adapter2.addPropertyChangeListener(changeReport2); adapter2.setBean(bean2); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport2.eventCount()); } public void testBeanChangeIgnoresOldBeanNull() { testBeanChangeIgnoresMissingOldOrNullValues(new ValueHolderWithOldValueNull(null)); } public void testBeanChangeIgnoresNewBeanNull() { testBeanChangeIgnoresMissingOldOrNullValues(new ValueHolderWithNewValueNull(null)); } public void testBeanChangeIgnoresOldAndNewBeanNull() { testBeanChangeIgnoresMissingOldOrNullValues(new ValueHolderWithOldAndNewValueNull(null)); } private void testBeanChangeIgnoresMissingOldOrNullValues(ValueModel beanChannel) { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); beanChannel.setValue(bean1); BeanAdapter adapter = new BeanAdapter(beanChannel, true); ValueModel model = adapter.getValueModel("readWriteObjectProperty"); PropertyChangeReport changeReport = new PropertyChangeReport(); model.addValueChangeListener(changeReport); beanChannel.setValue(bean2); bean1.setReadWriteObjectProperty("bean1value"); assertEquals("No event if modifying the old bean", 0, changeReport.eventCount()); bean2.setReadWriteObjectProperty("bean2value"); assertEquals("Fires event if modifying the new bean", 1, changeReport.eventCount()); } public void testChangedState() { BeanAdapter adapter = new BeanAdapter(model1, true); assertEquals("The initial changed state is false.", false, adapter.isChanged()); model1.setReadWriteObjectProperty("aBrandNewValue"); assertEquals("Changing the bean turns the changed state to true.", true, adapter.isChanged()); adapter.resetChanged(); assertEquals("Resetting changes turns the changed state to false.", false, adapter.isChanged()); model1.setReadWriteObjectProperty("anotherValue"); assertEquals("Changing the bean turns the changed state to true again.", true, adapter.isChanged()); adapter.setBean(model2); assertEquals("Changing the bean resets the changed state.", false, adapter.isChanged()); } public void testChangedStateFiresEvents() { BeanAdapter adapter = new BeanAdapter(model1, true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("changed", changeReport); model1.setReadWriteObjectProperty("aBrandNewValue"); adapter.resetChanged(); model1.setReadWriteObjectProperty("anotherValue"); adapter.setBean(model2); assertEquals("The changed state changed four times.", 4, changeReport.eventCount()); } // Testing Bean Property Changes ****************************************** public void testUnnamedBeanPropertyChange() { BeanAdapter adapter = new BeanAdapter(model1, true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addBeanPropertyChangeListener(changeReport); model1.setReadWriteObjectProperty("value1"); assertEquals("Setting a bound property forwards the PropertyChangeEvent.", 1, changeReport.eventCount()); model1.setReadWriteObjectProperties("value2", false, 42); assertEquals("Firing a multicast event forwards a PropertyChangeEvent too.", 2, changeReport.eventCount()); adapter.setBean(model2); assertEquals("Changing the bean fires no bean event.", 2, changeReport.eventCount()); model1.setReadWriteObjectProperty("ignoredValue1"); assertEquals("Setting a bound property in the old bean fires no event.", 2, changeReport.eventCount()); model2.setReadWriteObjectProperty("value2"); assertEquals("Setting a bound property forwards another PropertyChangeEvent.", 3, changeReport.eventCount()); } public void testNamedBeanPropertyChange() { BeanAdapter adapter = new BeanAdapter(model1, true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addBeanPropertyChangeListener("readWriteObjectProperty", changeReport); model1.setReadWriteObjectProperty("value1"); assertEquals("Setting a bound property forwards the PropertyChangeEvent.", 1, changeReport.eventCount()); model1.setReadWriteObjectProperties("value2", false, 42); assertEquals("Firing a multicast event forwards no event to named listeners.", 1, changeReport.eventCount()); adapter.setBean(model2); assertEquals("Changing the bean fires no bean event.", 1, changeReport.eventCount()); model1.setReadWriteObjectProperty("ignoredValue1"); assertEquals("Setting a bound property in the old bean fires no event.", 1, changeReport.eventCount()); model2.setReadWriteObjectProperty("value2"); assertEquals("Setting a bound property forwards another PropertyChangeEvent.", 2, changeReport.eventCount()); } // Misc ******************************************************************* /** * Checks that the cached PropertyDescriptor is available when needed. */ public void testPropertyDescriptorCache() { ValueModel beanChannel = new ValueHolder(null, true); BeanAdapter adapter = new BeanAdapter(beanChannel, true); ValueModel valueModel = adapter.getValueModel("readWriteObjectProperty"); beanChannel.setValue(new TestBean()); valueModel.setValue("Test"); } /** * Verifies that the factory method vends the same instance of the * adapting model if called multiple times. */ public void testVendsSameModel() { BeanAdapter adapter = new BeanAdapter(model1, true); Object adapter1 = adapter.getValueModel("readWriteObjectProperty"); Object adapter2 = adapter.getValueModel("readWriteObjectProperty"); assertSame("The adapter factory method vends the same instance.", adapter1, adapter2); } /** * Verifies that the BeanAdapter rejects attempts to get an adapting * ValueModel by means of #getValueModel with different * property accessor names. In other words, for each bean property * API users must use either {@link BeanAdapter#getValueModel(String)} or * {@link BeanAdapter#getValueModel(String, String, String)}, not both. * And all calls to the latter method must use the same getter and setter * names for the same property name.

* * This test invokes both methods for the same property name with different * getter and/or setter names and expects that the second call is rejected. * The BeanAdapter is created without a bean set, to avoid that the * BeanAdapter checks for a valid property. */ public void testRejectsGetValueModelWithDifferentAccessors() { String failureText = "The BeanAdapter must reject attempts " + "to get a ValueModel for the same property " + "with different accessor names."; String propertyName = "property"; String getterName1 = "getter1"; String getterName2 = "getter2"; String setterName1 = "setter1"; String setterName2 = "setter2"; BeanAdapter adapter1 = new BeanAdapter(null); adapter1.getValueModel(propertyName); try { adapter1.getValueModel(propertyName, getterName1, setterName1); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } BeanAdapter adapter2 = new BeanAdapter(null); adapter2.getValueModel(propertyName, getterName1, setterName1); try { adapter2.getValueModel(propertyName); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } BeanAdapter adapter3 = new BeanAdapter(null); adapter3.getValueModel(propertyName, getterName1, setterName1); try { adapter3.getValueModel(propertyName, getterName2, setterName1); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } BeanAdapter adapter4 = new BeanAdapter(null); adapter4.getValueModel(propertyName, getterName1, setterName1); try { adapter4.getValueModel(propertyName, getterName1, setterName2); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } } // Checking for ConcurrentModificationExceptions ************************** /** * Sets up an environment that has thrown a ConcurrentModificationException * in older releases. Basically, in a situation where the BeanAdapter * iterates over all its internal property adapters, we add a new adapter * and check if this fails. */ public void testCanGetModelWhileListeningToBeanChange() { BeanAdapter adapter = createConcurrentModificationEnvironment(); adapter.setBean(model2); } public void testCanGetModelWhileListeningToMulticastChange() { createConcurrentModificationEnvironment(); model1.setReadWriteObjectProperties("value3", true, 3); } private BeanAdapter createConcurrentModificationEnvironment() { model1.setReadWriteObjectProperty("value1"); model2.setReadWriteObjectProperty("value2"); final BeanAdapter adapter = new BeanAdapter(model1, true); ValueModel valueModel1 = adapter.getValueModel("readWriteObjectProperty"); // Add a second listener so that adding a new SimplePropertyAdapter // may cause a ConcurrentModificationException while adding a new adapter. adapter.getValueModel("readWriteBooleanProperty"); PropertyChangeListener listener = new PropertyChangeListener() { public void propertyChange(java.beans.PropertyChangeEvent evt) { adapter.getValueModel("readWriteIntProperty"); } }; valueModel1.addValueChangeListener(listener); return adapter; } // Null Bean ************************************************************** public void testNullBean() { BeanAdapter adapter = new BeanAdapter((TestBean) null, true); ValueModel model = adapter.getValueModel("readWriteObjectProperty"); model.toString(); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/CombinedTest.java0000644000175000017500000001263011374522114027013 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import javax.swing.JTextField; import junit.framework.TestCase; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.adapter.BasicComponentFactory; import com.jgoodies.binding.beans.Model; import com.jgoodies.binding.beans.PropertyAdapter; import com.jgoodies.binding.list.SelectionInList; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.collect.ArrayListModel; import com.jgoodies.common.collect.ObservableList; /** * A test case for changing a SelectionInList's list by changing an * underlying bean that holds a list. * * * @author Karsten Lentzsch * @version $Revision: 1.12 $ */ public final class CombinedTest extends TestCase { public void testPersonChangeWithPresentationModel() { testPersonChange(true); } public void testPersonChangeWithPropertyAdapter() { testPersonChange(false); } private void testPersonChange(boolean usePresentationModel) { String phoneNumber = "32168"; Person person1 = new Person(); person1.addPhone(new Phone(phoneNumber)); Person person2 = new Person(); PresentationModel masterModel = new PresentationModel(person1); SelectionInList sil = new SelectionInList( masterModel.getModel(Person.PROPERTYNAME_PHONES)); ValueModel selectionHolder = sil.getSelectionHolder(); ValueModel phoneNumberModel = usePresentationModel ? new PresentationModel(selectionHolder).getModel(Phone.PROPERTYNAME_NUMBER) : new PropertyAdapter(selectionHolder, Phone.PROPERTYNAME_NUMBER, true); JTextField phoneNumberField = BasicComponentFactory.createTextField(phoneNumberModel); assertEquals( "Person1, no number selected (model): ", null, phoneNumberModel.getValue()); assertEquals("Person1, no number selected (text): ", "", phoneNumberField.getText()); sil.setSelectionIndex(0); assertEquals("Person1, first number selected (model): ", phoneNumber, phoneNumberModel.getValue()); assertEquals( "Person1, first number selected (text): ", phoneNumber, phoneNumberField.getText()); masterModel.setBean(person2); assertEquals( "Person2, no number selected (model): ", null, phoneNumberModel.getValue()); assertEquals( "Person2, no number selected (text): ", "", phoneNumberField.getText()); } // Helper Classes ********************************************************* public static class Phone extends Model { public static final String PROPERTYNAME_NUMBER = "number"; private String number; Phone(String number) { this.number = number; } public String getNumber() { return number; } public void setNumber(String newNumber) { String oldNumber = getNumber(); number = newNumber; firePropertyChange(PROPERTYNAME_NUMBER, oldNumber, newNumber); } } public static final class Person extends Model { public static final String PROPERTYNAME_PHONES = "phones"; private final ObservableList phones; private Person() { phones = new ArrayListModel(); } public ObservableList getPhones() { return phones; } public void addPhone(Phone phone) { phones.add(phone); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/0000755000175000017500000000000011374522114024656 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/ObservableBean.java0000644000175000017500000000477511374522114030410 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; /** * A Java Bean that provides the minimal support for bound properties, * it can add and remove PropertyChangeListeners. * This class is intended to be used by the Binding unit test suite. * * @author Karsten * @version $Revision: 1.10 $ */ public class ObservableBean extends UnobservableBean { protected PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(this); public final synchronized void addPropertyChangeListener( PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } public final synchronized void removePropertyChangeListener( PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/TestBean.java0000644000175000017500000002146311374522114027234 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.PropertyVetoException; import com.jgoodies.binding.beans.Model; /** * A Java Bean that provides a bunch of properties. * The property types are: Object, boolean and int; * the access types are: read-write, read-only and write-only. * In addition, there's a property that doesn't check for changes * - and may cause problems with event notification cycles. * This class is intended to be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.21 $ */ public class TestBean extends Model { public Object readWriteObjectProperty; public boolean readWriteBooleanProperty; public int readWriteIntProperty; public Integer readWriteIntegerProperty; public String readWriteUpperCaseProperty; public Object readOnlyObjectProperty; public boolean readOnlyBooleanProperty; public int readOnlyIntProperty; public Object writeOnlyObjectProperty; public boolean writeOnlyBooleanProperty; public int writeOnlyIntProperty; public Object nullOldValueProperty; public Object nullNewValueProperty; public Object nullOldAndNewValueProperty; public String constrainedProperty; // Getters and Setters for the Read/Write Properties ********************** public Object getReadWriteObjectProperty() { return readWriteObjectProperty; } public void setReadWriteObjectProperty(Object newValue) { setReadWriteObjectProperty(newValue, false); } public void setReadWriteObjectProperty(Object newValue, boolean checkIdentity) { Object oldValue = getReadWriteObjectProperty(); readWriteObjectProperty = newValue; firePropertyChange("readWriteObjectProperty", oldValue, newValue, checkIdentity); } public boolean isReadWriteBooleanProperty() { return readWriteBooleanProperty; } public void setReadWriteBooleanProperty(boolean newValue) { boolean oldValue = isReadWriteBooleanProperty(); readWriteBooleanProperty = newValue; firePropertyChange("readWriteBooleanProperty", oldValue, newValue); } public int getReadWriteIntProperty() { return readWriteIntProperty; } public void setReadWriteIntProperty(int newValue) { int oldValue = getReadWriteIntProperty(); readWriteIntProperty = newValue; firePropertyChange("readWriteIntProperty", oldValue, newValue); } public Integer getReadWriteIntegerProperty() { return readWriteIntegerProperty; } public void setReadWriteIntegerProperty(Integer newValue) { Integer oldValue = getReadWriteIntegerProperty(); readWriteIntegerProperty = newValue; firePropertyChange("readWriteIntegerProperty", oldValue, newValue); } public String getReadWriteUpperCaseProperty() { return readWriteUpperCaseProperty; } public void setReadWriteUpperCaseProperty(String newValue) { String oldValue = getReadWriteUpperCaseProperty(); String newUpperCaseValue = newValue == null ? null : newValue.toUpperCase(); readWriteUpperCaseProperty = newUpperCaseValue; firePropertyChange("readWriteUpperCaseProperty", oldValue, newUpperCaseValue); } // Getters for the Read-Only Properties *********************************** public Object getReadOnlyObjectProperty() { return readOnlyObjectProperty; } public boolean isReadOnlyBooleanProperty() { return readOnlyBooleanProperty; } public int getReadOnlyIntProperty() { return readOnlyIntProperty; } // Setters for the Write-Only Properties ********************************** public void setWriteOnlyObjectProperty(Object newValue) { writeOnlyObjectProperty = newValue; firePropertyChange("writeOnlyObjectProperty", null, newValue); } public void setWriteOnlyBooleanProperty(boolean newValue) { writeOnlyBooleanProperty = newValue; firePropertyChange("writeOnlyBooleanProperty", null, Boolean.valueOf(newValue)); } public void setWriteOnlyIntProperty(int newValue) { writeOnlyIntProperty = newValue; firePropertyChange("writeOnlyIntProperty", null, new Integer(newValue)); } // Accessors for Properties that fire events with old or new value == null public Object getNullOldValueProperty() { return nullOldValueProperty; } public void setNullOldValueProperty(Object newValue) { nullOldValueProperty = newValue; firePropertyChange("nullOldValueProperty", null, newValue); } public Object getNullNewValueProperty() { return nullNewValueProperty; } public void setNullNewValueProperty(Object newValue) { Object oldValue = getNullNewValueProperty(); nullNewValueProperty = newValue; firePropertyChange("nullNewValueProperty", oldValue, null); } public Object getNullOldAndNewValueProperty() { return nullOldAndNewValueProperty; } public void setNullOldAndNewValueProperty(Object newValue) { nullOldAndNewValueProperty = newValue; firePropertyChange("nullOldAndNewValueProperty", null, null); } // Getters and Setters that Throw Runtime Exceptions ********************** /** * Throws a runtime exception. * * @return nothing - won't return */ public Object getNoAccess() { return new Integer(3 / 0); } /** * Throws a runtime exception. * * @param object an arbitrary object */ public void setNoAccess(Object object) { if (3 / 0 < 4 || object == null) return; } // Multiple Updates ******************************************************* public void setReadWriteObjectProperties(Object object, boolean b, int i) { readWriteObjectProperty = object; readWriteBooleanProperty = b; readWriteIntProperty = i; fireMultiplePropertiesChanged(); } // Firing Changes on the Read-Only Properties ***************************** public void fireChangeOnReadOnlyBooleanProperty(boolean newValue) { boolean oldValue = isReadOnlyBooleanProperty(); readOnlyBooleanProperty = newValue; firePropertyChange("readOnlyBooleanProperty", oldValue, newValue); } public void fireChangeOnReadOnlyObjectProperty(Object newValue) { Object oldValue = getReadOnlyObjectProperty(); readOnlyObjectProperty = newValue; firePropertyChange("readOnlyObjectProperty", oldValue, newValue); } // Constrained Properties ************************************************* public String getConstrainedProperty() { return constrainedProperty; } public void setConstrainedProperty(String newValue) throws PropertyVetoException { String oldValue = getConstrainedProperty(); fireVetoableChange("constrainedProperty", oldValue, newValue); constrainedProperty = newValue; firePropertyChange("constrainedProperty", oldValue, newValue); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/EquityTestBean.java0000644000175000017500000000455511374522114030440 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; /** * Unlike its superclass TestBean, this class override #equals * and compares instances by comparing their key property. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ */ public final class EquityTestBean extends TestBean { private final String key; public EquityTestBean(String key) { this.key = key; } @Override public boolean equals(Object object) { if (this == object) return true; if (!(object instanceof EquityTestBean)) return false; return key == ((EquityTestBean) object).key; } @Override public int hashCode() { return key.hashCode(); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/ReadOnlyBean.java0000644000175000017500000000402511374522114030025 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import com.jgoodies.binding.beans.Model; /** * A Java Bean that provides a read-only Object property. * This class is intended to be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ */ public class ReadOnlyBean extends Model { Object property; public Object getProperty() { return property; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/WriteOnlyBean.java0000644000175000017500000000416711374522114030253 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import com.jgoodies.binding.beans.Model; /** * A Java Bean with a write-only property that is intended to * be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ */ public class WriteOnlyBean extends Model { String property; public void setProperty(String newValue) { String oldValue = property; property = newValue; firePropertyChange("property", oldValue, newValue); } } ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/ObservableBeanWithNamedPCLSupport.javajgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/ObservableBeanWithNamedPCLSupport.j0000644000175000017500000000472211374522114033445 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.PropertyChangeListener; /** * An observable Java Bean that provides additional support to * add and remove PropertyChangeListeners for a named property. * This class is intended to be used by the Binding unit test suite. * * @author Karsten * @version $Revision: 1.10 $ */ public class ObservableBeanWithNamedPCLSupport extends ObservableBean { public final synchronized void addPropertyChangeListener( String propertyName, PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(propertyName, listener); } public final synchronized void removePropertyChangeListener( String propertyName, PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(propertyName, listener); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/package.html0000644000175000017500000000504111374522114027137 0ustar twernertwerner Contains classes useful to test compliance with the Java Bean specification.

Related Documentation

For more information see: @see com.jgoodies.binding.tests @see com.jgoodies.binding.tests.event @see com.jgoodies.binding.tests.value jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/CustomBean.java0000644000175000017500000001360211374522114027563 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import com.jgoodies.binding.beans.Model; /** * A Java Bean that provides a bunch of properties. * The property types are: Object, boolean and int; * the access types are: read-write, read-only and write-only. * In addition, there's a property that doesn't check for changes * - and may cause problems with event notification cycles. * This class is intended to be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ */ public final class CustomBean extends Model { public Object readWriteObjectProperty; public boolean readWriteBooleanProperty; public int readWriteIntProperty; public Integer readWriteIntegerProperty; public Object readOnlyObjectProperty; public boolean readOnlyBooleanProperty; public int readOnlyIntProperty; public Object writeOnlyObjectProperty; public boolean writeOnlyBooleanProperty; public int writeOnlyIntProperty; // Getters and Setters for the Read/Write Properties ********************** public Object getReadWriteObjectProperty() { return readWriteObjectProperty; } public void setReadWriteObjectProperty(Object newValue) { Object oldValue = getReadWriteObjectProperty(); readWriteObjectProperty = newValue; firePropertyChange("readWriteObjectProperty", oldValue, newValue); } public boolean isReadWriteBooleanProperty() { return readWriteBooleanProperty; } public void setReadWriteBooleanProperty(boolean newValue) { boolean oldValue = isReadWriteBooleanProperty(); readWriteBooleanProperty = newValue; firePropertyChange("readWriteBooleanProperty", oldValue, newValue); } public int getReadWriteIntProperty() { return readWriteIntProperty; } public void setReadWriteIntProperty(int newValue) { int oldValue = getReadWriteIntProperty(); readWriteIntProperty = newValue; firePropertyChange("readWriteIntProperty", oldValue, newValue); } public Integer getReadWriteIntegerProperty() { return readWriteIntegerProperty; } public void setReadWriteIntegerProperty(Integer newValue) { Integer oldValue = getReadWriteIntegerProperty(); readWriteIntegerProperty = newValue; firePropertyChange("readWriteIntegerProperty", oldValue, newValue); } // Getters for the Read-Only Properties *********************************** public Object getReadOnlyObjectProperty() { return readOnlyObjectProperty; } public boolean isReadOnlyBooleanProperty() { return readOnlyBooleanProperty; } public int getReadOnlyIntProperty() { return readOnlyIntProperty; } // Setters for the Write-Only Properties ********************************** public void setWriteOnlyObjectProperty(Object newValue) { writeOnlyObjectProperty = newValue; firePropertyChange("writeOnlyObjectProperty", null, newValue); } public void setWriteOnlyBooleanProperty(boolean newValue) { writeOnlyBooleanProperty = newValue; firePropertyChange("writeOnlyBooleanProperty", null, Boolean.valueOf(newValue)); } public void setWriteOnlyIntProperty(int newValue) { writeOnlyIntProperty = newValue; firePropertyChange("writeOnlyIntProperty", null, new Integer(newValue)); } // Getters and Setters that Throw Runtime Exceptions ********************** /** * Throws a runtime exception. * * @return nothing - won't return */ public Object getNoAccess() { return new Integer(3 / 0); } /** * Throws a runtime exception. * * @param object an arbitrary object */ public void setNoAccess(Object object) { if (3 / 0 < 4 || object == null) return; } // Multiple Updates ******************************************************* public void setReadWriteObjectProperties(Object object, boolean b, int i) { readWriteObjectProperty = object; readWriteBooleanProperty = b; readWriteIntProperty = i; fireMultiplePropertiesChanged(); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/BeanWithPCLAdder.java0000644000175000017500000000455711374522114030534 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; /** * A Java Bean that can add a PropertyChangeListener but cannot remove one. * It doesn't support bound properties, or in other words is unobservable. * This class is intended to be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ */ public class BeanWithPCLAdder extends UnobservableBean { private final PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(this); public final synchronized void addPropertyChangeListener( PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/CustomAccessBean.java0000644000175000017500000001133111374522114030702 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import com.jgoodies.binding.beans.Model; /** * A Java Bean that provides a bunch of properties that * are accessed via custom accessors that do not follow the * Java Bean naming conventions. * This class is intended to be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ */ public final class CustomAccessBean extends Model { public Object readWriteObjectProperty; public boolean readWriteBooleanProperty; public int readWriteIntProperty; public Object readOnlyObjectProperty; public boolean readOnlyBooleanProperty; public int readOnlyIntProperty; public Object writeOnlyObjectProperty; public boolean writeOnlyBooleanProperty; public int writeOnlyIntProperty; // Getters and Setters for the Read/Write Properties ********************** public Object readRWObjectProperty() { return readWriteObjectProperty; } public void writeRWObjectProperty(Object newValue) { Object oldValue = readRWObjectProperty(); readWriteObjectProperty = newValue; firePropertyChange("readWriteObjectProperty", oldValue, newValue); } public boolean readRWBooleanProperty() { return readWriteBooleanProperty; } public void writeRWBooleanProperty(boolean newValue) { boolean oldValue = readRWBooleanProperty(); readWriteBooleanProperty = newValue; firePropertyChange("readWriteBooleanProperty", oldValue, newValue); } public int readRWIntProperty() { return readWriteIntProperty; } public void writeRWIntProperty(int newValue) { int oldValue = readRWIntProperty(); readWriteIntProperty = newValue; firePropertyChange("readWriteIntProperty", oldValue, newValue); } public Object readROObjectProperty() { return readOnlyObjectProperty; } public boolean readROBooleanProperty() { return readOnlyBooleanProperty; } public int readROIntProperty() { return readOnlyIntProperty; } // Setters for the Write-Only Properties ********************************** public void writeWOObjectProperty(Object newValue) { writeOnlyObjectProperty = newValue; firePropertyChange("writeOnlyObjectProperty", null, newValue); } public void writeWOBooleanProperty(boolean newValue) { writeOnlyBooleanProperty = newValue; firePropertyChange("writeOnlyBooleanProperty", null, Boolean.valueOf(newValue)); } public void writeWOIntProperty(int newValue) { writeOnlyIntProperty = newValue; firePropertyChange("writeOnlyIntProperty", null, new Integer(newValue)); } // Getters and Setters that Throw Runtime Exceptions ********************** public void setReadWriteObjectProperties(Object object, boolean b, int i) { readWriteObjectProperty = object; readWriteBooleanProperty = b; readWriteIntProperty = i; fireMultiplePropertiesChanged(); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/BeanClasses.java0000644000175000017500000001324311374522114027707 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Consists of a bunch of Java Bean classes and Bean-like classes * that are used by the PropertyAdapter unit tests. * * @author Karsten Lentzsch * @version $Revision: 1.14 $ */ public final class BeanClasses { private static final Class[] OBSERVABLE_CLASSES = {ObservableBean.class, ObservableBeanWithNamedPCLSupport.class }; private static final Class[] UNOBSERVABLE_CLASSES = {UnobservableBean.class, BeanWithPCLAdder.class, BeanWithPCLRemover.class, UnobservableBeanWithNamedPCLSupport.class }; private BeanClasses() { // Override default constructor; prevents instantiation. } // Accessing Bean Classes ************************************************ /** * Returns a list of bean classes that are observable, * i.e. that provide support for bound properties. * * @return an unmodifiable list of observable bean classes */ public static List> getObservableClasses() { return Collections.unmodifiableList(Arrays.asList(OBSERVABLE_CLASSES)); } /** * Returns a list of bean classes that are not observable, * i.e. that do not provide support for bound properties. * * @return an unmodifiable list of unobservable bean classes */ public static List> getUnobservableClasses() { return Collections.unmodifiableList(Arrays.asList(UNOBSERVABLE_CLASSES)); } // Accessing Bean Instance *********************************************** /** * Returns a bean that provides support for bound properties. * * @return an observable bean */ public static Object getObservable() { return new ObservableBean(); } /** * Returns an array of bean instances that provide support * for bound properties. * * @return an array of observable beans instances */ public static List getObservableBeans() { List beans = new ArrayList(); for (Class beanClass : OBSERVABLE_CLASSES) { try { beans.add(beanClass.newInstance()); } catch (Exception e) { // Should not happen in this context } } return beans; } /** * Returns a bean that provides no support for bound properties. * * @return an unobservable bean */ public static Object getUnobservable() { return new UnobservableBean(); } /** * Returns an array of bean instances that provide no support * for bound properties. * * @return an array of unobservable beans instances */ public static List getUnobservableBeans() { List beans = new ArrayList(); for (Class beanClass : UNOBSERVABLE_CLASSES) { try { beans.add(beanClass.newInstance()); } catch (Exception e) { // Should not happen in this context } } return beans; } /** * Returns an array of bean instances where some provide * support for bound properties, and others do not. * * @return an array of beans instances */ public static List getBeans() { List beans = new ArrayList(); for (Class beanClass : OBSERVABLE_CLASSES) { try { beans.add(beanClass.newInstance()); } catch (Exception e) { // Should not happen in this context } } for (Class beanClass : UNOBSERVABLE_CLASSES) { try { beans.add(beanClass.newInstance()); } catch (Exception e) { // Should not happen in this context } } return beans; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/ReadWriteHierarchyBean.java0000644000175000017500000000433211374522114032036 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; /** * A Java Bean that inherits a property reader and provides the * writer for that property. Can be used to property detection * and access that is spread over different classes in a hierarchy. * This class is intended to be used by the Binding unit test suite. * * @author Karsten * @version $Revision: 1.11 $ */ public class ReadWriteHierarchyBean extends ReadOnlyBean { public void setProperty(Object newValue) { Object oldValue = property; property = newValue; firePropertyChange("property", oldValue, newValue); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/BeanWithPCLRemover.java0000644000175000017500000000456711374522114031135 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; /** * A Java Bean that can remove a PropertyChangeListener but cannot add one. * It doesn't support bound properties, or in other words is unobservable. * This class is intended to be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ */ public class BeanWithPCLRemover extends UnobservableBean { private final PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(this); public final synchronized void removePropertyChangeListener( PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } } ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/UnobservableBeanWithNamedPCLSupport.javajgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/UnobservableBeanWithNamedPCLSupport0000644000175000017500000000524411374522114033560 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; /** * An unobservable Java Bean that can add and remove PropertyChangeListeners * for a named property but that lacks support for the general multicast * PropertyChangeListeners. This class is intended to * be used by the Binding unit test suite. * * @author Karsten * @version $Revision: 1.11 $ */ public class UnobservableBeanWithNamedPCLSupport extends UnobservableBean { private final PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(this); public final synchronized void addPropertyChangeListener( String propertyName, PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(propertyName, listener); } public final synchronized void removePropertyChangeListener( String propertyName, PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(propertyName, listener); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/ReadWriteBean.java0000644000175000017500000000431611374522114030201 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import com.jgoodies.binding.beans.Model; /** * A Java Bean that provides a read-write String property. * This class is intended to be used by the Binding unit test suite. * * @author Karsten * @version $Revision: 1.10 $ */ public class ReadWriteBean extends Model { private String property; public String getProperty() { return property; } public void setProperty(String newValue) { String oldValue = property; property = newValue; firePropertyChange("property", oldValue, newValue); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/UnobservableBean.java0000644000175000017500000000425611374522114030745 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; /** * An unobservable Java Bean that can neither add nor remove * PropertyChangeListeners. * This class is intended to be used by the Binding unit test suite. * * @author Karsten * @version $Revision: 1.10 $ */ public class UnobservableBean { private Object property; public Object getProperty() { return property; } /** * @param newProperty the new value for property */ public void setProperty(Object newProperty) { this.property = newProperty; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/VetoableChangeRejector.java0000644000175000017500000000417511374522114032075 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.PropertyChangeEvent; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; /** * Rejects all vetoable changes. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ */ public final class VetoableChangeRejector implements VetoableChangeListener { public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { throw new PropertyVetoException("I veto against all changes.", evt); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/beans/CustomBeanBeanInfo.java0000644000175000017500000000600711374522114031166 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.beans; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.beans.SimpleBeanInfo; /** * Provides custom PropertyDescriptors that describe * a subset of the available properties. * This class is intended to be used by the Binding unit test suite. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ */ public final class CustomBeanBeanInfo extends SimpleBeanInfo { /** * Gets the beans PropertyDescriptors. * * @return An array of PropertyDescriptors describing the editable * properties supported by this bean. May return null if the * information should be obtained by automatic analysis.

* * If a property is indexed, then its entry in the result array will * belong to the IndexedPropertyDescriptor subclass of PropertyDescriptor. * A client of getPropertyDescriptors can use "instanceof" to check * if a given PropertyDescriptor is an IndexedPropertyDescriptor. */ @Override public PropertyDescriptor[] getPropertyDescriptors() { try { return new PropertyDescriptor[] { new PropertyDescriptor( "readWriteObjectProperty", CustomBean.class), }; } catch (IntrospectionException e) { return null; } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/ComboBoxAdapterTest.java0000644000175000017500000004300311374522114030302 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.util.Arrays; import javax.swing.DefaultListModel; import junit.framework.TestCase; import com.jgoodies.binding.adapter.ComboBoxAdapter; import com.jgoodies.binding.list.SelectionInList; import com.jgoodies.binding.tests.event.ListDataReport; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * Tests the {@link ComboBoxAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.29 $ * * @see ComboBoxAdapter */ public final class ComboBoxAdapterTest extends TestCase { private static final String[] AN_ARRAY = {"one", "two", "three"}; private DefaultListModel listModel; private SelectionInList selectionInList; private ValueModel selectionHolder; private ComboBoxAdapter combo; private ListDataReport report; @Override protected void setUp() throws Exception { super.setUp(); initComboWithListModel(); } @Override protected void tearDown() throws Exception { super.tearDown(); listModel = null; combo = null; selectionInList = null; report = null; } /** * Checks that the constructors reject null selectionHolders with a NPE. */ public void testConstructorRejectsNullValues() { final String message = "Constructor must throw NPE on null selectionHolder."; try { new ComboBoxAdapter(listModel, null); fail(message); } catch (NullPointerException e) { // the expected behaviour } try { new ComboBoxAdapter(AN_ARRAY, null); fail(message); } catch (NullPointerException e) { // the expected behaviour } try { new ComboBoxAdapter(Arrays.asList(AN_ARRAY), null); fail(message); } catch (NullPointerException e) { // the expected behaviour } try { new ComboBoxAdapter((SelectionInList)null); fail("Constructor must reject a null selectionInList."); } catch (NullPointerException e) { // the expected behaviour } } /** * Tests that the event source for all events fired by * ComboBoxModel operations is the ComboBoxModel. */ public void testEventSource() { final String message = "The event source must be the combo box model."; assertEquals("Initial size must be " + AN_ARRAY.length, AN_ARRAY.length, combo.getSize()); listModel.addElement("some"); assertEquals(message, combo, report.lastEvent().getSource()); listModel.remove(0); assertEquals(message, combo, report.lastEvent().getSource()); listModel.setElementAt("newElement", 0); assertEquals(message, combo, report.lastEvent().getSource()); combo.setSelectedItem("three"); assertEquals(message, combo, report.lastEvent().getSource()); } /** * Checks that manipulations on the underlying ListModel * throw one event only, or in other words: avoid duplicate events. */ public void testOneEventOnListModelManipulation() { assertOneEventOnListModelManipulation(); listModel = createListModel(AN_ARRAY); selectionInList = new SelectionInList(listModel); initComboWithSelectionInList(); assertOneEventOnListModelManipulation(); } private void assertOneEventOnListModelManipulation() { listModel.addElement("some"); assertEquals("event count", 1, report.eventCount()); listModel.removeElementAt(0); assertEquals("event count", 2, report.eventCount()); listModel.setElementAt("newElement", 0); assertEquals("event count", 3, report.eventCount()); } // public void testInitialSelectionInList() { // selectionHolder.setValue(listModel.getElementAt(1)); // SelectionInList sil = new SelectionInList(listModel); // combo = new ComboBoxAdapter(sil, selectionHolder); // assertEquals("Selection index shall reflect the initial selection.", // 1, // sil.getSelectionIndex()); // } // // public void testInitialSelectionInCombo() { // selectionHolder.setValue("notInList"); // SelectionInList sil = new SelectionInList(listModel); // sil.setSelectionIndex(1); // combo = new ComboBoxAdapter(sil, selectionHolder); // assertEquals("Selection index shall reflect the initial selection.", // -1, // sil.getSelectionIndex()); // } /** * Checks that a selection action throws one event only. */ public void testOneEventOnComboSelection() { assertOneEventOnComboSelection(); initComboWithArray(); assertOneEventOnComboSelection(); initComboWithList(); assertOneEventOnComboSelection(); initComboWithSelectionInList(); assertOneEventOnComboSelection(); } private void assertOneEventOnComboSelection() { combo.setSelectedItem("three"); assertEquals("event count", 1, report.eventCount()); } /** * Checks that a change in the selection holder value throws one event only. */ public void testOneEventOnHolderSelection() { assertOneEventOnHolderSelection(); initComboWithArray(); assertOneEventOnHolderSelection(); initComboWithList(); assertOneEventOnHolderSelection(); } private void assertOneEventOnHolderSelection() { selectionHolder.setValue("three"); assertEquals("event count", 1, report.eventCount()); } public void testAllowSelectionNotInList() { combo.setSelectedItem("notInList"); assertEquals("combo must accept any selection", "notInList", combo.getSelectedItem()); } /** * Checks that an absent element should be rejected * if the adapter is constructed with a selectionInList. */ public void testRejectSelectionNotInList() { initComboWithSelectionInList(); combo.setSelectedItem("notInList"); assertNull("combo must reject not contained elements", combo.getSelectedItem()); } public void testChangeListSameSelection() { initComboWithSelectionInList(); String[] anotherArray = new String[]{"one", "two", "three", "four"}; selectionInList.setListModel(createListModel(anotherArray)); assertTrue("combo must fire at least one event, either 1 content change or a combination of add/remove or content/add or content/remove", report.eventCount() >= 1); } // Changing the Selection ************************************************* /** * Verifies that a ComboBoxAdapter built with a SelectionInList * keeps the selection in synch with an underlying ListModel. */ public void testSelectionAfterSetElement() { initComboWithSelectionInList(); combo.setSelectedItem("two"); listModel.set(1, "other"); assertEquals("combo selection must be changed", "other", combo.getSelectedItem()); } /** * Verifies that a ComboBoxAdapter built with a selection holder * won't update the selection if the underlying ListModel changes. */ public void testSelectionAfterSetElementDecoupled() { combo.setSelectedItem("two"); listModel.set(1, "other"); assertEquals("combo selection must be changed", "two", combo.getSelectedItem()); } // Removing Items, Requires an Underlying ListModel *********************** public void testSelectionAfterRemoveBefore() { testSelectionAfterRemove(AN_ARRAY[1], 0); } public void testSelectionAfterRemoveAfter() { testSelectionAfterRemove(AN_ARRAY[1], 2); } /** * Verifies that the selection will be cleared if * it has been removed from the underlying list. */ public void testSelectionAfterRemoveOn() { initComboWithSelectionInList(); combo.setSelectedItem("two"); listModel.remove(1); assertEquals("Combo's selection must be empty", null, combo.getSelectedItem()); } /** * Verifies that the selection holder is unchanged * if the selection is removed from the underlying list. */ public void testSelectionAfterRemoveOnDecoupled() { combo.setSelectedItem("two"); listModel.remove(1); assertEquals("Combo's selection must be empty", "two", combo.getSelectedItem()); } public void testSelectionAfterRemoveUnrelated() { testSelectionAfterRemove("notInList", 1); } private void testSelectionAfterRemove(Object initialSelection, int index) { combo.setSelectedItem(initialSelection); listModel.remove(index); assertEquals("Combo's selection must be unchanged after remove", initialSelection, combo.getSelectedItem()); } /** * This is for comparison only: * taking DefaultComboBoxModel as reference. */ // public void testDefaultComboSelectionAfterRemoveOn() { // DefaultComboBoxModel combo = new DefaultComboBoxModel(someArray); // combo.setSelectedItem(someArray[1]); // combo.removeElementAt(1); // // PENDING: how independent is the combo selection? remove // // if removed from list? // // DefaultComboBoxModel moves selection to previous item // assertEquals("pathological behaviour of DefaultComboBoxModel ", "two", combo.getSelectedItem()); // // } // Inserting Items, Requires an underlying ListModel ********************** public void testSelectionAfterInsertBefore() { testSelectionAfterInsert(AN_ARRAY[1], 0); } public void testSelectionAfterInsertAfter() { testSelectionAfterInsert(AN_ARRAY[1], 2); } public void testSelectionAfterInsertOn() { testSelectionAfterInsert(AN_ARRAY[1], 1); } public void testSelectionAfterInsertUnrelated() { testSelectionAfterInsert("unrelated", 1); } private void testSelectionAfterInsert(Object initialSelection, int index) { combo.setSelectedItem(initialSelection); updateReport(); int oldSize = combo.getSize(); listModel.insertElementAt("another", index); assertEquals("Combo's selection must be unchanged after insert", initialSelection, combo.getSelectedItem()); assertEquals("size", oldSize + 1, combo.getSize()); } // List Changes, Requires an Underlying SelectionInList ******************* /** * Tests effects of setting new List when * selectionInList controls selection. */ public void testNewListWithSelectedItem() { // selection is controlled by SelectionInList! initComboWithSelectionInList(); combo.setSelectedItem("two"); updateReport(); Object[] array = {"totally", "different", "two", "content"}; selectionInList.setListModel(createListModel(array)); assertEquals("selection must be unchanged", "two", combo.getSelectedItem()); } /** * Tests effects of setting new List when * selectionInList controls selection. */ public void testNewListWithoutSelectedItem() { // The selection is controlled by SelectionInList! initComboWithSelectionInList(); combo.setSelectedItem("two"); updateReport(); Object[] array = {"totally", "different", "content"}; selectionInList.setListModel(createListModel(array)); assertEquals("combo selection must be null", null, combo.getSelectedItem()); } /** * Tests effects of setting new List when * selectionInList does not control selection. */ /* public void testNewListWithoutSelectedItemOldWasNotInList() { // The selection is _not_ controlled by SelectionInList! initComboWithSelectionInListAndSelection(); combo.setSelectedItem("notInList"); updateReport(); Object[] array = { "totally", "different", "content" }; selectionInList.setListModel(createListModel(array)); assertEquals("combo selection must be unchanged", "notInList", combo.getSelectedItem()); } */ /** * Tests effects of setting new List when * selectionInList does not control selection. */ // public void testNewListWithoutSelectedItemOldWasInList() { // // The selection is _not_ controlled by SelectionInList! // initComboWithSelectionInListAndSelection(); // combo.setSelectedItem("one"); // updateReport(); // Object[] array = { "totally", "different", "content" }; // selectionInList.setListModel(createListModel(array)); // assertEquals("combo selection must be null", null, combo.getSelectedItem()); // } // // // public void testChangeSelectionInList() { // initComboWithSelectionInListAndSelection(); // selectionInList.setSelection(AN_ARRAY[0]); // assertEquals( // "combo selection must be updated to selectionInList selection", // selectionInList.getSelection(), // combo.getSelectedItem()); // assertEquals("combo must fire one contentsChanged", // 1, // report.eventCountChange()); // updateReport(); // combo.setSelectedItem("notInList"); // assertNull("selectionInList selection must be empty", // selectionInList.getSelection()); // assertEquals("combo must fire one contentsChanged", // 1, // report.eventCountChange()); // selectionInList.setSelection(AN_ARRAY[1]); // assertEquals("combo selection must (or not??) be synched", // AN_ARRAY[1], // combo.getSelectedItem()); // } // Setup Helper Code ****************************************************** private void updateReport() { report = new ListDataReport(); combo.addListDataListener(report); } private void initComboWithSelectionInList() { initBasicFields(); combo = new ComboBoxAdapter(selectionInList); updateReport(); } private void initComboWithArray() { initBasicFields(); combo = new ComboBoxAdapter(AN_ARRAY, selectionHolder); updateReport(); } private void initComboWithListModel() { initBasicFields(); combo = new ComboBoxAdapter(listModel, selectionHolder); updateReport(); } private void initComboWithList() { initBasicFields(); combo = new ComboBoxAdapter(Arrays.asList(AN_ARRAY), selectionHolder); updateReport(); } private void initBasicFields() { listModel = createListModel(AN_ARRAY); selectionInList = new SelectionInList(listModel); selectionHolder = new ValueHolder(null); } private DefaultListModel createListModel(Object[] array) { DefaultListModel model = new DefaultListModel(); for (Object element : array) { model.addElement(element); } return model; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/AbstractConverterTest.java0000644000175000017500000001276111374522114030733 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import junit.framework.TestCase; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.value.AbstractConverter; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link AbstractConverter}. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ */ public final class AbstractConverterTest extends TestCase { private ValueHolder subject; private ValueModel converter; /** * @throws Exception in case of an unexpected problem */ @Override protected void setUp() throws Exception { super.setUp(); subject = new ValueHolder(); converter = new TestConverter(subject); } /** * @throws Exception in case of an unexpected problem */ @Override protected void tearDown() throws Exception { super.tearDown(); converter = null; subject = null; } // Tests ****************************************************************** public void testGetValueConversion() { Integer value = new Integer(1); Object expectedConversion = "1"; subject.setValue(value); assertEquals("The int " + value + " is converted to " + expectedConversion, expectedConversion, converter.getValue()); } public void testSetValueConversion() { Object convertedValue = "1"; Integer expectedValue = new Integer(1); converter.setValue(convertedValue); assertEquals("The converter value " + convertedValue + " is converted to " + expectedValue, expectedValue, subject.getValue()); } public void testPropertyChangeEventConversion() { Integer oldSubjectValue = null; Integer newSubjectValue = new Integer(1); Object expectedOldValue = null; Object expectedNewValue = "1"; subject.setValue(oldSubjectValue); PropertyChangeReport subjectReport = new PropertyChangeReport(); PropertyChangeReport converterReport = new PropertyChangeReport(); subject.addValueChangeListener(subjectReport); converter.addValueChangeListener(converterReport); subject.setValue(newSubjectValue); assertEquals("The old subject event value is " + oldSubjectValue, oldSubjectValue, subjectReport.lastOldValue()); assertEquals("The old converter event value is " + expectedOldValue, expectedOldValue, converterReport.lastOldValue()); assertEquals("The new subject event value is " + newSubjectValue, newSubjectValue, subjectReport.lastNewValue()); assertEquals("The new converter event value is " + expectedNewValue, expectedNewValue, converterReport.lastNewValue()); } // Test Converter ********************************************************* /** * An example converter that converts Integers to their String * representations. Cannot convert {@code null}. */ private static final class TestConverter extends AbstractConverter { private TestConverter(ValueModel valueModel) { super(valueModel); } /** * Converts the given value to its string representation. * * @param subjectValue the subject's value * @return the string representation of the given value */ @Override public Object convertFromSubject(Object subjectValue) { return subjectValue.toString(); } public void setValue(Object newValue) { subject.setValue(new Integer(Integer.parseInt((String) newValue))); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/SelectionInListTest.java0000644000175000017500000016176311374522114030357 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.swing.*; import javax.swing.event.ListDataEvent; import javax.swing.table.TableModel; import junit.framework.TestCase; import com.jgoodies.binding.adapter.AbstractTableAdapter; import com.jgoodies.binding.adapter.BasicComponentFactory; import com.jgoodies.binding.adapter.SingleListSelectionAdapter; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.beans.Model; import com.jgoodies.binding.beans.PropertyAdapter; import com.jgoodies.binding.list.SelectionInList; import com.jgoodies.binding.tests.beans.VetoableChangeRejector; import com.jgoodies.binding.tests.event.ListDataReport; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.tests.value.ValueHolderWithOldValueNull; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.collect.ArrayListModel; import com.jgoodies.common.collect.LinkedListModel; import com.jgoodies.common.collect.ObservableList; /** * A test case for class {@link SelectionInList}. * * @author Karsten Lentzsch * @version $Revision: 1.48 $ */ public final class SelectionInListTest extends TestCase { private static final Object[] AN_ARRAY = {"one", "two", "three"}; private DefaultListModel listModel; // Initialization ********************************************************* @Override protected void setUp() throws Exception { super.setUp(); listModel = createListModel(AN_ARRAY); } // Testing Constructors *************************************************** public void testConstructorRejectsNullListModelHolder() { try { new SelectionInList((ValueModel) null); fail("The SelectionInList must reject a null list holder."); } catch (NullPointerException e) { // The expected behavior. } } public void testConstructorRejectsNonIdentityCheckingValueHolder() { try { new SelectionInList(new ValueHolder(listModel)); fail("The SelectionInList must reject list holders that have the idenity check is disabled."); } catch (IllegalArgumentException e) { // The expected behavior. } } public void testConstructorRejectsInvalidListHolderContent() { try { new SelectionInList(new ValueHolder("Hello", true)); fail("The SelectionInList must reject list holder content other than List or ListModel."); } catch (ClassCastException e) { // The expected behavior. } } // Testing Setup ********************************************************** public void testRejectNullSelectionIndex() { ValueHolder indexHolder = new ValueHolder(0); SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndexHolder(indexHolder); try { indexHolder.setValue(null); fail("The SelectionInList must reject null selection index values."); } catch (Exception e) { // The expected behavior. } } public void testRejectNewNullSelectionHolder() { SelectionInList sil = new SelectionInList(listModel); try { sil.setSelectionHolder(null); fail("The SelectionInList must reject a null selection holder."); } catch (Exception e) { // The expected behavior. } } public void testRejectNewNullSelectionIndexHolder() { SelectionInList sil = new SelectionInList(listModel); try { sil.setSelectionIndexHolder(null); fail("The SelectionInList must reject a null selection index holder."); } catch (Exception e) { // The expected behavior. } } public void testRejectNewSelectionIndexHolderWithNullValue() { SelectionInList sil = new SelectionInList(listModel); try { sil.setSelectionIndexHolder(new ValueHolder(null)); fail("The SelectionInList must reject selection index holder with null values."); } catch (Exception e) { // The expected behavior. } } public void testSelectionIndexAfterInitialization() { Object value = "two"; ValueModel selectionHolder = new ValueHolder(value); SelectionInList sil = new SelectionInList(listModel, selectionHolder); int initialSelectionIndex = sil.getSelectionIndex(); assertEquals( "The initial selection index reflects the index of the selection holder's initial value.", listModel.indexOf(value), initialSelectionIndex); selectionHolder.setValue(null); assertEquals( "The selection index has been updated to indicate no selection.", -1, sil.getSelectionIndex()); } public void testSelectionIndexValid() { SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndex(-1); for (int i = 0; i < sil.getSize()-1; i++) { sil.setSelectionIndex(i); } sil.setList(null); sil.setSelectionIndex(-1); } public void testSelectionIndexLowerBound() { SelectionInList sil = new SelectionInList(listModel); try { sil.setSelectionIndex(-2); fail("A selection index < -1 must be rejected."); } catch (IndexOutOfBoundsException e) { // The expected behavior } } public void testSelectionIndexUpperBound() { SelectionInList sil = new SelectionInList(listModel); try { sil.setSelectionIndex(sil.getSize()); fail("A selection index > list size must be rejected."); } catch (IndexOutOfBoundsException e) { // The expected behavior } sil.setList(null); try { sil.setSelectionIndex(0); fail("A selection index of 0 must be rejected for an empty or absent list."); } catch (IndexOutOfBoundsException e) { // The expected behavior } } public void testDefaultSelectionHolderChecksIdentity() { SelectionInList sil = new SelectionInList(listModel); // The following line will fail if the default selection holder // doesn't check the identity. BeanAdapter adapter = new BeanAdapter(sil.getSelectionHolder()); assertEquals("The BeanAdapter's bean is the selection.", sil.getSelection(), adapter.getBean()); } // Testing Bean Spec Compliance ******************************************* public void testAcceptsNullOldValueInSelectionIndexPropertyChangeEvent() { int selectionIndex = 0; ValueHolder indexHolder = new ValueHolder(selectionIndex); SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndexHolder(indexHolder); indexHolder.fireValueChange(null, new Integer(selectionIndex)); assertEquals("Selection index", selectionIndex, sil.getSelectionIndex()); Object selection = listModel.get(selectionIndex); assertEquals("Selection", selection, sil.getSelection()); } public void testAcceptsNullNewValueInSelectionIndexPropertyChangeEvent() { int selectionIndex = 0; ValueHolder indexHolder = new ValueHolder(selectionIndex); SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndexHolder(indexHolder); indexHolder.fireValueChange(new Integer(selectionIndex), null); assertEquals("Selection index", selectionIndex, sil.getSelectionIndex()); Object selection = listModel.get(selectionIndex); assertEquals("Selection", selection, sil.getSelection()); } public void testAcceptsNullOldAndNewValueInSelectionIndexPropertyChangeEvent() { int selectionIndex = 0; ValueHolder indexHolder = new ValueHolder(selectionIndex); SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndexHolder(indexHolder); indexHolder.fireValueChange(null, null); assertEquals("Selection index", selectionIndex, sil.getSelectionIndex()); Object selection = listModel.get(selectionIndex); assertEquals("Selection", selection, sil.getSelection()); } // ************************************************************************ public void testFiresSelectionChangeOnlyForSelectionChanges() { int selectionIndex = 0; ValueHolder indexHolder = new ValueHolder(selectionIndex); SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndexHolder(indexHolder); // Create change reports. PropertyChangeReport valueReport = new PropertyChangeReport(); PropertyChangeReport selectionReport = new PropertyChangeReport(); PropertyChangeReport selectionEmptyReport = new PropertyChangeReport(); PropertyChangeReport selectionIndexReport = new PropertyChangeReport(); // Register change reports for value, selection, selection index, // and selectionEmpty sil.addValueChangeListener(valueReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION, selectionReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_EMPTY, selectionEmptyReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_INDEX, selectionIndexReport); indexHolder.fireValueChange(null, new Integer(selectionIndex)); indexHolder.fireValueChange(new Integer(selectionIndex), null); indexHolder.fireValueChange(null, null); assertEquals("No value change event fired", 0, valueReport.eventCount()); assertEquals("No selection change event fired", 0, selectionReport.eventCount()); assertEquals("No selectionEmpty change event fired", 0, selectionEmptyReport.eventCount()); assertEquals("No selectionIndex change event fired", 0, selectionIndexReport.eventCount()); } public void testIndexChangeFiresChangesWithNonNullOldValue() { int initialSelectionIndex = 0; int newSelectionIndex = 1; AbstractValueModel indexHolder = new ValueHolderWithOldValueNull(initialSelectionIndex); SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndexHolder(indexHolder); // Create change reports. PropertyChangeReport valueReport = new PropertyChangeReport(); PropertyChangeReport selectionReport = new PropertyChangeReport(); PropertyChangeReport selectionIndexReport = new PropertyChangeReport(); // Register change reports for value, selection, selection index, // and selectionEmpty sil.addValueChangeListener(valueReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION, selectionReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_INDEX, selectionIndexReport); // We change the selection index holder's value to the new index. // The ValueModel used for the selectionIndexHolder fires no old value. indexHolder.setValue(newSelectionIndex); Object oldValue = valueReport.lastOldValue(); Object oldSelection = selectionReport.lastOldValue(); Object oldSelectionIndex = selectionIndexReport.lastOldValue(); assertTrue("Non-null old value in value change event", oldValue != null); assertTrue("Non-null old value in selection change event", oldSelection != null); assertTrue("Non-null old value in selectionIndex change event", oldSelectionIndex != null); } public void testSelectionForDirectSelectionIndexChanges() { SelectionInList sil = new SelectionInList(listModel); // Create change reports. PropertyChangeReport valueReport = new PropertyChangeReport(); PropertyChangeReport selectionReport = new PropertyChangeReport(); PropertyChangeReport selectionEmptyReport = new PropertyChangeReport(); PropertyChangeReport selectionIndexReport = new PropertyChangeReport(); // Register change reports for value, selection, selection index, // and selectionEmpty sil.addValueChangeListener(valueReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION, selectionReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_EMPTY, selectionEmptyReport); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_INDEX, selectionIndexReport); assertEquals("The initial value is null.", null, sil.getValue()); assertEquals("The initial selection is null.", null, sil.getSelection()); assertTrue("The initial selection is empty.", sil.isSelectionEmpty()); assertEquals("The initial selection index is -1.", -1, sil.getSelectionIndex()); sil.setSelectionIndex(0); assertEquals("The new value is the first list element.", listModel.getElementAt(0), sil.getValue()); assertEquals("The new selection is the first list element.", listModel.getElementAt(0), sil.getSelection()); assertFalse("The selection is not empty.", sil.isSelectionEmpty()); assertEquals("The new selection index is 0.", 0, sil.getSelectionIndex()); assertEquals("selectionEmpty changed from true to false.", 1, selectionEmptyReport.eventCount()); assertTrue("1) selectionEmpty change event oldValue == true.", selectionEmptyReport.lastOldBooleanValue()); assertFalse("1) selectionEmpty change event newValue == false.", selectionEmptyReport.lastNewBooleanValue()); sil.setSelectionIndex(1); assertFalse("The selection index is 1 and not empty.", sil.isSelectionEmpty()); assertEquals("No selectionEmpty change event fired", 1, selectionEmptyReport.eventCount()); sil.clearSelection(); assertTrue("The selection index is empty.", sil.isSelectionEmpty()); assertEquals("selectionEmpty changed from false to true.", 2, selectionEmptyReport.eventCount()); assertFalse("2) selectionEmpty change event oldValue == false.", selectionEmptyReport.lastOldBooleanValue()); assertTrue("2) selectionEmpty change event newValue == true.", selectionEmptyReport.lastNewBooleanValue()); } public void testSelectionForIndirectSelectionIndexChanges() { SelectionInList sil = new SelectionInList(listModel); ValueHolder selectionIndexHolder = new ValueHolder(-1); sil.setSelectionIndexHolder(selectionIndexHolder); PropertyChangeReport selectionEmptyReport = new PropertyChangeReport(); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_EMPTY, selectionEmptyReport); assertTrue("The initial selection is empty.", sil.isSelectionEmpty()); selectionIndexHolder.setValue(0); assertFalse("The selection index is 0 and not empty.", sil.isSelectionEmpty()); assertEquals("selectionEmpty changed from true to false.", 1, selectionEmptyReport.eventCount()); assertTrue("1) selectionEmpty change event oldValue == true.", selectionEmptyReport.lastOldBooleanValue()); assertFalse("1) selectionEmpty change event newValue == false.", selectionEmptyReport.lastNewBooleanValue()); selectionIndexHolder.setValue(1); assertFalse("The selection index is 1 and not empty.", sil.isSelectionEmpty()); assertEquals("No selectionEmpty change event fired", 1, selectionEmptyReport.eventCount()); selectionIndexHolder.setValue(-1); assertTrue("The selection index is empty.", sil.isSelectionEmpty()); assertEquals("selectionEmpty changed from false to true.", 2, selectionEmptyReport.eventCount()); assertFalse("2) selectionEmpty change event oldValue == false.", selectionEmptyReport.lastOldBooleanValue()); assertTrue("2) selectionEmpty change event newValue == true.", selectionEmptyReport.lastNewBooleanValue()); } public void testSelectionForDirectSelectionChanges() { SelectionInList sil = new SelectionInList(listModel); PropertyChangeReport selectionEmptyReport = new PropertyChangeReport(); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_EMPTY, selectionEmptyReport); sil.setSelection((String) listModel.getElementAt(0)); assertFalse("The selection index is 0 and not empty.", sil.isSelectionEmpty()); assertEquals("selectionEmpty changed from true to false.", 1, selectionEmptyReport.eventCount()); assertTrue("1) selectionEmpty change event oldValue == true.", selectionEmptyReport.lastOldBooleanValue()); assertFalse("1) selectionEmpty change event newValue == false.", selectionEmptyReport.lastNewBooleanValue()); sil.setSelection((String) listModel.getElementAt(1)); assertFalse("The selection index is 1 and not empty.", sil.isSelectionEmpty()); assertEquals("No selectionEmpty change event fired", 1, selectionEmptyReport.eventCount()); sil.setSelection(null); assertTrue("The selection index is empty.", sil.isSelectionEmpty()); assertEquals("selectionEmpty changed from false to true.", 2, selectionEmptyReport.eventCount()); assertFalse("2) selectionEmpty change event oldValue == false.", selectionEmptyReport.lastOldBooleanValue()); assertTrue("2) selectionEmpty change event newValue == true.", selectionEmptyReport.lastNewBooleanValue()); } public void testSelectionForIndirectSelectionChanges() { ValueModel selectionHolder = new ValueHolder(); SelectionInList sil = new SelectionInList(new ValueHolder(listModel, true), selectionHolder); PropertyChangeReport selectionEmptyReport = new PropertyChangeReport(); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION_EMPTY, selectionEmptyReport); assertTrue("The selection index is -1 and empty.", sil.isSelectionEmpty()); selectionHolder.setValue(listModel.getElementAt(0)); assertFalse("The selection index is 0 and not empty.", sil.isSelectionEmpty()); assertEquals("selectionEmpty changed from true to false.", 1, selectionEmptyReport.eventCount()); assertTrue("1) selectionEmpty change event oldValue == true.", selectionEmptyReport.lastOldBooleanValue()); assertFalse("1) selectionEmpty change event newValue == false.", selectionEmptyReport.lastNewBooleanValue()); selectionHolder.setValue(listModel.getElementAt(1)); assertFalse("The selection index is 1 and not empty.", sil.isSelectionEmpty()); assertEquals("No selectionEmpty change event fired", 1, selectionEmptyReport.eventCount()); selectionHolder.setValue(null); assertTrue("The selection index is empty.", sil.isSelectionEmpty()); assertEquals("selectionEmpty changed from false to true.", 2, selectionEmptyReport.eventCount()); assertFalse("2) selectionEmpty change event oldValue == false.", selectionEmptyReport.lastOldBooleanValue()); assertTrue("2) selectionEmpty change event newValue == true.", selectionEmptyReport.lastNewBooleanValue()); } // Selection In Synch With the Selection Index After List Operations ****** public void testSelectionReflectsIndexAfterClear() { ValueModel selectionHolder = new ValueHolder(); SelectionInList sil = new SelectionInList(new ValueHolder(listModel, true), selectionHolder); sil.setSelectionIndex(1); PropertyChangeReport changeReport = new PropertyChangeReport(); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION, changeReport); assertEquals("SelectionHolder holds the second list element.", listModel.get(1), selectionHolder.getValue()); listModel.clear(); assertEquals("The selection index is -1.", -1, sil.getSelectionIndex()); assertEquals("The selection is null.", null, sil.getSelection()); assertEquals("The selection holder value is null.", null, sil.getSelectionHolder().getValue()); assertEquals("A selection change event has been fired.", 1, changeReport.eventCount()); // If this event provides an old value, it should be the old value. if (changeReport.lastOldValue() != null) { assertEquals("The selection change's old value is 'two'.", "two", changeReport.lastOldValue()); } assertEquals("The selection change new value is null.", null, changeReport.lastNewValue()); } public void testSelectionReflectsIndexAfterIndexMoveBack() { ValueModel selectionHolder = new ValueHolder(); SelectionInList sil = new SelectionInList(new ValueHolder(listModel, true), selectionHolder); sil.setSelectionIndex(1); PropertyChangeReport changeReport = new PropertyChangeReport(); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION, changeReport); Object initialSelection = listModel.get(1); assertEquals("SelectionHolder holds the second list element.", initialSelection, selectionHolder.getValue()); listModel.remove(0); Object expectedSelection = listModel.get(0); assertEquals("The selection index has been decreased by 1.", 0, sil.getSelectionIndex()); assertEquals("The selection remains unchanged.", expectedSelection, sil.getSelection()); assertEquals("The selection holder value remains unchanged.", expectedSelection, sil.getSelectionHolder().getValue()); assertEquals("No selection change event has been fired.", 0, changeReport.eventCount()); } public void testSelectionReflectsIndexAfterIndexMoveForward() { ValueModel selectionHolder = new ValueHolder(); SelectionInList sil = new SelectionInList(new ValueHolder(listModel, true), selectionHolder); sil.setSelectionIndex(1); PropertyChangeReport changeReport = new PropertyChangeReport(); sil.addPropertyChangeListener(SelectionInList.PROPERTYNAME_SELECTION, changeReport); Object initialSelection = listModel.get(1); assertEquals("SelectionHolder holds the second list element.", initialSelection, selectionHolder.getValue()); listModel.add(0, "zero"); Object expectedSelection = listModel.get(2); assertEquals("The selection index has been increased by 1.", 2, sil.getSelectionIndex()); assertEquals("The selection remains unchanged.", expectedSelection, sil.getSelection()); assertEquals("The selection holder value remains unchanged.", expectedSelection, sil.getSelectionHolder().getValue()); assertEquals("No selection change event has been fired.", 0, changeReport.eventCount()); } // Properties Must be Changed Before the PropertyChangeEvent is Fired ***** public void testSelectionChangeEventFiredAfterSelectionChange() { final SelectionInList sil = new SelectionInList(listModel); sil.getSelectionHolder().setValue("one"); sil.addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { assertEquals("Value equals valueChange's new value.", sil.getValue(), evt.getNewValue()); } }); sil.addPropertyChangeListener( SelectionInList.PROPERTYNAME_SELECTION, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { assertEquals("Selection equals selectionChange's new value.", sil.getSelection(), evt.getNewValue()); } }); sil.getSelectionHolder().setValue("two"); } public void testSelectionEmptyChangeEventFiredAfterSelectionEmptyChange() { final SelectionInList sil = new SelectionInList(listModel); sil.getSelectionHolder().setValue("kuckuck"); sil.addPropertyChangeListener( SelectionInList.PROPERTYNAME_SELECTION_EMPTY, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { assertEquals("Selection empty equals selectionEmtpyChange's new value.", Boolean.valueOf(sil.isSelectionEmpty()), evt.getNewValue()); } }); sil.getSelectionHolder().setValue("two"); } public void testSelectionIndexChangeEventFiredAfterSelectionIndexChange() { final SelectionInList sil = new SelectionInList(listModel); sil.getSelectionIndexHolder().setValue(new Integer(1)); sil.addPropertyChangeListener( SelectionInList.PROPERTYNAME_SELECTION_INDEX, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { assertEquals("Selection index equals selectionIndexChange's new value.", new Integer(sil.getSelectionIndex()), evt.getNewValue()); } }); sil.getSelectionIndexHolder().setValue(new Integer(2)); } // ListModel Operations Affect the Selection and Selection Index ********** public void testContentsChangedOnSelection() { SelectionInList sil = new SelectionInList(listModel); final int initialSelection = 2; sil.setSelectionIndex(initialSelection); assertEquals("Initial selection index", initialSelection, sil.getSelectionIndex()); listModel.setElementAt("another", initialSelection); assertEquals("Index after re-setting the element at selection index", initialSelection, sil.getSelectionIndex()); assertEquals("sil value must be the updated element", "another", sil.getSelection()); assertEquals("selectionHolder value must equal sil value", sil.getSelection(), sil.getSelectionHolder().getValue()); } public void testIgnoreContentsChangedOnMinusOne() { testIgnoreContentsChanged(2, "another"); } public void testIgnoreContentsChangedOnMinusOneEmptySelection() { testIgnoreContentsChanged(-1, "two"); } private void testIgnoreContentsChanged(int silSelectionIndex, Object comboSelection) { ComboBoxModel comboBoxModel = new UnforgivingComboBoxModel(AN_ARRAY); SelectionInList sil = new SelectionInList(comboBoxModel); sil.setSelectionIndex(silSelectionIndex); Object silSelection = sil.getSelection(); comboBoxModel.setSelectedItem(comboSelection); assertEquals("Initial selection index", silSelectionIndex, sil .getSelectionIndex()); assertEquals("sil value must be unchanged", silSelection, sil .getSelection()); } public void testInsertBeforeSelectionIncreasesSelectionIndex() { SelectionInList sil = new SelectionInList(listModel); final int initialSelection = 2; sil.setSelectionIndex(initialSelection); assertEquals("Initial selection index", initialSelection, sil.getSelectionIndex()); listModel.insertElementAt("another", 0); assertEquals("Index after inserting an element before the selection", initialSelection + 1, sil.getSelectionIndex()); } public void testInsertBeforeSelectionKeepsSelection() { SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndex(2); assertEquals("Initial selection index", 2, sil.getSelectionIndex()); Object selection = sil.getSelection(); listModel.insertElementAt("another", 0); assertEquals("Selection after inserting an element", selection, sil.getSelection()); } public void testInsertAfterSelectionKeepsSelectionIndex() { SelectionInList sil = new SelectionInList(listModel); final int initialSelection = 1; sil.setSelectionIndex(initialSelection); assertEquals("Initial selection index", initialSelection, sil.getSelectionIndex()); listModel.insertElementAt("another", initialSelection + 1); assertEquals("Index after inserting an element after the selection", initialSelection, sil.getSelectionIndex()); } public void testRemoveBeforeSelectionDecreasesSelectionIndex() { SelectionInList sil = new SelectionInList(listModel); final int initialSelection = 2; sil.setSelectionIndex(initialSelection); assertEquals("Initial selection index", initialSelection, sil.getSelectionIndex()); listModel.remove(0); assertEquals("Selection index after removing an element", initialSelection - 1, sil.getSelectionIndex()); } public void testRemoveBeforeSelectionKeepsSelection() { SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndex(2); assertEquals("Initial selection index", 2, sil.getSelectionIndex()); Object selection = sil.getSelection(); listModel.remove(0); assertEquals("Selection after removing an element", selection, sil.getSelection()); } /** * Removes the selected first element from a non-empty list and * checks whether the selection index is reset to -1. */ public void testRemoveSelectedFirstElementResetsSelectionIndex() { SelectionInList sil = new SelectionInList(listModel); final int initialSelectionIndex = 0; sil.setSelectionIndex(initialSelectionIndex); assertEquals("Selection index before the remove action", initialSelectionIndex, sil.getSelectionIndex()); listModel.remove(initialSelectionIndex); assertEquals("Selection index after the removal of the selected element", -1, sil.getSelectionIndex()); } /** * Removes the selected last element from a non-empty list and * checks whether the selection index is reset to -1. */ public void testRemoveSelectedLastElementResetsSelectionIndex() { SelectionInList sil = new SelectionInList(listModel); final int initialSelectionIndex = listModel.getSize() - 1; sil.setSelectionIndex(initialSelectionIndex); assertEquals("Selection index before the remove action", initialSelectionIndex, sil.getSelectionIndex()); listModel.remove(initialSelectionIndex); assertEquals("Selection index after the removal of the selected element", -1, sil.getSelectionIndex()); } public void testRemoveAfterSelectionKeepsSelectionIndex() { SelectionInList sil = new SelectionInList(listModel); final int initialSelectionIndex = 0; sil.setSelectionIndex(initialSelectionIndex); assertEquals("Initial selection index", initialSelectionIndex, sil.getSelectionIndex()); listModel.remove(1); assertEquals("Selection index after removing an element", initialSelectionIndex, sil.getSelectionIndex()); } // Keeping the Selection on List Changes ********************************** /** * Changes the listHolder's (list) value and checks how * the SelectionInList keeps or resets the selection. * The listHolder is a ValueHolder that * reports an old and a new value. */ public void testKeepsSelectionOnListChange() { testKeepsSelectionOnListChange(new ValueHolder(null, true), false); } /** * Changes the listHolder's (list) value and checks how * the SelectionInList keeps or resets the selection. * The listHolder is a ValueHolder that * reports an old and a new value. */ public void testKeepsTableSelectionOnListChange() { testKeepsSelectionOnListChange(new ValueHolder(null, true), true); } /** * Changes the listHolder's (list) value and checks how * the SelectionInList keeps or resets the selection. * The listHolder is a ForgetfulValueHolder that * uses null as old value when reporting value changes. */ public void testKeepsSelectionOnListChangeNoOldList() { testKeepsSelectionOnListChange(new ValueHolderWithOldValueNull(null), false); } /** * Changes the listHolder's (list) value and checks how * the SelectionInList keeps or resets the selection. * The listHolder is a ForgetfulValueHolder that * uses null as old value when reporting value changes. */ public void testKeepsTableSelectionOnListChangeNoOldList() { testKeepsSelectionOnListChange(new ValueHolderWithOldValueNull(null), true); } /** * Changes the listHolder's (list) value and checks how * the SelectionInList keeps or resets the selection. * If specified, the SelectionInList will be bound to a JTable * using an AbstractTableAdapter and a SingleSelectionAdapter. * Since the JTable may clear the selection after some updates, * this tests if the selection is restored. * * @param listHolder the ValueModel that holds the list * @param bindToTable if true, the SelectionInList will be bound * to a JTable */ private void testKeepsSelectionOnListChange(ValueModel listHolder, boolean bindToTable) { List list1 = new ArrayList(); list1.add("One"); list1.add("Two"); list1.add("Three"); List list2 = new ArrayList(list1); List list3 = new ArrayList(); list3.add("Three"); list3.add(new String("Two")); list3.add("One"); List list4 = new ArrayList(list1); list4.add("Four"); List list5 = list1.subList(0, 2); List list6 = new ArrayList(); list6.add("One"); list6.add("Three"); list6.add(new String("Two")); listHolder.setValue(list1); SelectionInList sil = new SelectionInList(listHolder); sil.setSelectionIndex(1); if (bindToTable) { TableModel tableModel = new AbstractTableAdapter(sil, new String[]{"Name"}){ public Object getValueAt(int rowIndex, int columnIndex) { return getRow(rowIndex); }}; JTable table = new JTable(tableModel); table.setSelectionModel(new SingleListSelectionAdapter(sil.getSelectionIndexHolder())); } Object oldSelection = sil.getSelection(); assertEquals("List1: Selection is 'Two'.", "Two", oldSelection); listHolder.setValue(list2); assertEquals("List2: Selection index is still 1.", 1, sil.getSelectionIndex()); assertEquals("List2: Selection is still 'Two'.", "Two", sil.getSelection()); listHolder.setValue(list3); assertEquals("List3: Selection index is still 1.", 1, sil.getSelectionIndex()); assertEquals("List3: Selection is still 'Two'.", "Two", sil.getSelection()); listHolder.setValue(list4); assertEquals("List4: Selection index is still 1.", 1, sil.getSelectionIndex()); assertEquals("List4: Selection is still 'Two'.", "Two", sil.getSelection()); listHolder.setValue(list5); assertEquals("List5: Selection index is still 1.", 1, sil.getSelectionIndex()); assertEquals("List5: Selection is still 'Two'.", "Two", sil.getSelection()); listHolder.setValue(list6); assertEquals("List6: Selection index is now 2.", 2, sil.getSelectionIndex()); assertEquals("List6: Selection is still 'Two'.", "Two", sil.getSelection()); listHolder.setValue(new ArrayList()); assertEquals("Selection index is -1.", -1, sil.getSelectionIndex()); assertEquals("Selection is null.", null, sil.getSelection()); listHolder.setValue(list1); assertEquals("Selection index is still -1.", -1, sil.getSelectionIndex()); assertEquals("Selection is still null.", null, sil.getSelection()); } // List Change Events ***************************************************** /** * Tests the SelectionInList ListDataEvents fired during list changes. * The transitions are {} -> {} -> {a, b} -> {b, c} -> {a, b, c} -> {}. */ public void testListChangeEvents() { List list1 = Collections.emptyList(); List list2 = Collections.emptyList(); List list3 = Arrays.asList("a", "b"); List list4 = Arrays.asList("b", "c"); List list5 = Arrays.asList("a", "b", "c"); List list6 = Collections.emptyList(); SelectionInList sil = new SelectionInList(list1); ListDataReport report = new ListDataReport(); sil.addListDataListener(report); sil.setList(list2); assertEquals("The transition {} -> {} fires no ListDataEvent.", 0, report.eventCount()); report.clearEventList(); sil.setList(list3); assertEquals("The transition {} -> {a, b} fires 1 add event.", 1, report.eventCount()); assertEvent("The transition {} -> {a, b} fires an add event with interval[0, 1].", ListDataEvent.INTERVAL_ADDED, 0, 1, report.lastEvent()); report.clearEventList(); sil.setList(list4); assertEquals("The transition {a, b} -> {b, c} fires 1 add event.", 1, report.eventCount()); assertEvent("The transition {a, b} -> {b, c} fires an add event with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); sil.setList(list5); assertEquals("The transition {a, b} -> {b, c, d} fires two events.", 2, report.eventCount()); assertEvent("The transition {b, c} -> {a, b, c} fires an add event with interval[2, 2].", ListDataEvent.INTERVAL_ADDED, 2, 2, report.previousEvent()); assertEvent("The transition {b, c} -> {a, b, c} fires a contents changed with interval[0, 1].", ListDataEvent.CONTENTS_CHANGED, 0, 1, report.lastEvent()); report.clearEventList(); sil.setList(list6); assertEquals("The transition {b, c, d} -> {} fires one event.", 1, report.eventCount()); assertEvent("The transition {b, c, d} -> {} fires a remove event with interval[0, 1].", ListDataEvent.INTERVAL_REMOVED, 0, 2, report.lastEvent()); } private void assertEvent(String description, int eventType, int index0, int index1, ListDataEvent event) { assertEquals("Type: " + description, eventType, event.getType()); assertEquals("Index0: " + description, index0, event.getIndex0()); assertEquals("Index1: " + description, index1, event.getIndex1()); } // Resetting the selection if the new list is empty or null public void testResetsSelectionIndexOnNullOrEmptyList() { SelectionInList sil = new SelectionInList(listModel); sil.setSelectionIndex(1); sil.setList(Collections.emptyList()); assertEquals("Selection index is -1.", -1, sil.getSelectionIndex()); assertEquals("Selection is still null.", null, sil.getSelection()); sil.setListModel(listModel); sil.setSelectionIndex(1); sil.setList(null); assertEquals("Selection index is -1.", -1, sil.getSelectionIndex()); assertEquals("Selection is still null.", null, sil.getSelection()); } // Firing ListDataEvents ************************************************** /** * Checks that list data events from an underlying are reported * by the SelectionInList. */ public void testFiresListModelListDataEvents() { PropertyChangeReport changeReport = new PropertyChangeReport(); ListDataReport listDataReport1 = new ListDataReport(); ListDataReport listDataReport2 = new ListDataReport(); ArrayListModel arrayListModel = new ArrayListModel(); SelectionInList sil = new SelectionInList((ListModel) arrayListModel); arrayListModel.addListDataListener(listDataReport1); sil.addListDataListener(listDataReport2); sil.addValueChangeListener(changeReport); arrayListModel.add("One"); assertEquals("No list change.", changeReport.eventCount(), 0); assertEquals("An element has been added.", listDataReport2.eventCount(), 1); assertEquals("An element has been added.", listDataReport2 .eventCountAdd(), 1); arrayListModel.addAll(Arrays.asList(new String[]{"two", "three", "four"})); assertEquals("No list change.", changeReport.eventCount(), 0); assertEquals("An element block has been added.", listDataReport2.eventCount(), 2); assertEquals("An element block has been added.", listDataReport2 .eventCountAdd(), 2); arrayListModel.remove(0); assertEquals("An element has been removed.", listDataReport2.eventCount(), 3); assertEquals("No element has been added.", listDataReport2 .eventCountAdd(), 2); assertEquals("An element has been removed.", listDataReport2 .eventCountRemove(), 1); arrayListModel.set(1, "newTwo"); assertEquals("An element has been replaced.", listDataReport2.eventCount(), 4); assertEquals("No element has been added.", listDataReport2 .eventCountAdd(), 2); assertEquals("No element has been removed.", listDataReport2 .eventCountRemove(), 1); assertEquals("An element has been changed.", listDataReport2 .eventCountChange(), 1); // Compare the event counts of the list models listener // with the SelectionInList listener. assertEquals("Add event counts are equal.", listDataReport1.eventCountAdd(), listDataReport2.eventCountAdd()); assertEquals("Remove event counts are equal.", listDataReport1.eventCountRemove(), listDataReport2.eventCountRemove()); assertEquals("Change event counts are equal.", listDataReport1.eventCountChange(), listDataReport2.eventCountChange()); } // Registering, Deregistering and Registering of the ListDataListener ***** /** * Checks and verifies that the SelectionInList registers * its ListDataListener with the underlying ListModel once only. * In other words: the SelectionInList doesn't register * its ListDataListener multiple times.

* * Uses a list holder that checks the identity and * reports an old and new value. */ public void testSingleListDataListener() { testSingleListDataListener(new ValueHolder(null, true)); } /** * Checks and verifies that the SelectionInList registers * its ListDataListener with the underlying ListModel once only. * In other words: the SelectionInList doesn't register * its ListDataListener multiple times.

* * Uses a list holder uses null as old value when reporting value changes. */ public void testSingleListDataListenerNoOldList() { testSingleListDataListener(new ValueHolderWithOldValueNull(null)); } /** * Checks and verifies that the SelectionInList registers * its ListDataListener with the underlying ListModel once only. * In other words: the SelectionInList doesn't register * its ListDataListener multiple times. */ private void testSingleListDataListener(ValueModel listHolder) { new SelectionInList(listHolder); ArrayListModel listModel1 = new ArrayListModel(); LinkedListModel listModel2 = new LinkedListModel(); listHolder.setValue(listModel1); assertEquals("SelectionInList registered its ListDataListener.", 1, listModel1.getListDataListeners().length); listHolder.setValue(listModel1); assertEquals("SelectionInList reregistered its ListDataListener.", 1, listModel1.getListDataListeners().length); listHolder.setValue(listModel2); assertEquals("SelectionInList deregistered its ListDataListener.", 0, listModel1.getListDataListeners().length); assertEquals("SelectionInList registered its ListDataListener.", 1, listModel2.getListDataListeners().length); } /** * Checks and verifies for a bunch of ListModel instances, * whether the ListDataListener has been reregistered properly. */ public void testReregisterListDataListener() { ObservableList empty1 = new ArrayListModel(); ObservableList empty2 = new ArrayListModel(); testReregistersListDataListener(empty1, empty2); ObservableList empty3 = new LinkedListModel(); ObservableList empty4 = new LinkedListModel(); testReregistersListDataListener(empty3, empty4); ObservableList array1 = new ArrayListModel(); ObservableList array2 = new ArrayListModel(); array1.add(Boolean.TRUE); array2.add(Boolean.TRUE); testReregistersListDataListener(array1, array2); ObservableList linked1 = new LinkedListModel(); ObservableList linked2 = new LinkedListModel(); linked1.add(Boolean.TRUE); linked2.add(Boolean.TRUE); testReregistersListDataListener(linked1, linked2); } /** * Checks and verifies whether the ListDataListener has been * reregistered properly. This will fail if the change support * fails to fire a change event when the instance changes.

* * Creates a SelectionInList on list1, then changes it to list2, * modifies boths lists, and finally checks whether the SelectionInList * has fired the correct events. */ private void testReregistersListDataListener( ObservableList list1, ObservableList list2) { ListDataReport listDataReport1 = new ListDataReport(); ListDataReport listDataReport2 = new ListDataReport(); ListDataReport listDataReportSel = new ListDataReport(); SelectionInList sil = new SelectionInList((ListModel) list1); // Change the list model. // Changes on list1 shall not affect the SelectionInList. // Changes in list2 shall be the same as for the SelectionInList. sil.setListModel(list2); list1.addListDataListener(listDataReport1); list2.addListDataListener(listDataReport2); sil.addListDataListener(listDataReportSel); // Modify both list models. list1.add("one1"); list1.add("two1"); list1.add("three1"); list1.add("four1"); list1.remove(1); list1.remove(0); list1.set(0, "newOne1"); list1.set(1, "newTwo1"); assertEquals("Events counted for list model 1", 8, listDataReport1.eventCount()); assertEquals("No events counted for list model 2", 0, listDataReport2.eventCount()); assertEquals("No events counted for the SelectionInList", 0, listDataReportSel.eventCount()); list2.add("one2"); list2.add("two2"); list2.add("three2"); list2.remove(1); list2.set(0, "newOne2"); assertEquals("Events counted for list model 2", 5, listDataReport2.eventCount()); assertEquals("Events counted for the SelectionInList", 5, listDataReportSel.eventCount()); // Compare the event lists. assertEquals("Events for list2 and SelectionInList differ.", listDataReport2, listDataReportSel); } // Handling Vetoes ********************************************************* public void testHandlesVetoedIndexChange() { ConstrainedIndexBean cib = new ConstrainedIndexBean(0); ValueModel selectionHolder = new ValueHolder(); ValueModel indexHolder = new PropertyAdapter(cib, "index", true); SelectionInList sil = new SelectionInList(listModel, selectionHolder, indexHolder); JComboBox combo = BasicComponentFactory.createComboBox(sil); assertEquals("Initial bean index is SelectionInList index.", cib.getIndex(), sil.getSelectionIndex()); sil.setSelectionIndex(1); assertEquals("Changed bean index is SelectionInList index.", cib.getIndex(), sil.getSelectionIndex()); assertEquals("Changed bean index is combo index.", cib.getIndex(), combo.getSelectedIndex()); cib.addVetoableChangeListener(new VetoableChangeRejector()); sil.setSelectionIndex(2); assertEquals("Old bean index is SelectionInList index.", cib.getIndex(), sil.getSelectionIndex()); assertEquals("Old bean index is combo index.", cib.getIndex(), combo.getSelectedIndex()); } // Helper Code ************************************************************ private DefaultListModel createListModel(Object[] array) { DefaultListModel model = new DefaultListModel(); for (Object element : array) { model.addElement(element); } return model; } /** * A DefaultComboBoxModel that fires when accessing illegal * index (instead of silently returning null). * DefaultCombo is mad anyway - should return the selected item on -1... */ private static final class UnforgivingComboBoxModel extends DefaultComboBoxModel { UnforgivingComboBoxModel(Object[] elements) { super(elements); } @Override public Object getElementAt(int index) { if (index < 0 || index >= getSize()) { throw new ArrayIndexOutOfBoundsException(index); } return super.getElementAt(index); } } public static final class ConstrainedIndexBean extends Model { private int index; ConstrainedIndexBean(int index) { this.index = index; } public int getIndex() { return index; } public void setIndex(int newValue) throws PropertyVetoException { int oldValue = getIndex(); fireVetoableChange("index", oldValue, newValue); index = newValue; firePropertyChange("index", oldValue, newValue); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/BasicComponentFactoryTest.java0000644000175000017500000002545511374522114031540 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.awt.Color; import java.util.Date; import javax.swing.JFormattedTextField; import javax.swing.JPasswordField; import javax.swing.JTextArea; import javax.swing.JTextField; import junit.framework.TestCase; import com.jgoodies.binding.adapter.BasicComponentFactory; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link BasicComponentFactory}. * * @author Karsten Lentzsch * @version $Revision: 1.13 $ */ public final class BasicComponentFactoryTest extends TestCase { // Color Chooser ********************************************************** public void testRejectPlainColorChooserCreationWithInitialNullValue() { ValueModel valueModel = new ValueHolder(); try { BasicComponentFactory.createColorChooser(valueModel); fail("The ValueModel's value must not be null at creation time."); } catch (NullPointerException e) { // The expected behavior } } public void testPlainColorChooserWithNullValue() { ValueModel valueModel = new ValueHolder(Color.RED); BasicComponentFactory.createColorChooser(valueModel); valueModel.setValue(null); } public void testRejectColorChooserCreationWithNullDefaultColor() { ValueModel valueModel = new ValueHolder(Color.RED); try { BasicComponentFactory.createColorChooser(valueModel, null); fail("The default color must not be null."); } catch (NullPointerException e) { // The expected behavior } } public void testAcceptColorChooserCreationWithInitialNullValue() { ValueModel valueModel = new ValueHolder(); BasicComponentFactory.createColorChooser(valueModel, Color.RED); } // Dates ****************************************************************** public void testDateFieldMapsNullToEmpty() { ValueModel dateModel = new ValueHolder(); JFormattedTextField dateField = BasicComponentFactory.createDateField( dateModel); assertEquals("Date field maps null to an empty string.", "", dateField.getText()); dateModel.setValue(new Date(32168)); setTextAndCommitEdit(dateField, ""); assertEquals("Date field maps the empty string to null.", null, dateModel.getValue()); dateModel.setValue(new Date(424242)); setTextAndCommitEdit(dateField, " "); assertEquals("Date field maps blank strings to null.", null, dateModel.getValue()); } public void testDateFieldIsValidOnEmptyAndBlank() { JFormattedTextField dateField = BasicComponentFactory.createDateField( new ValueHolder()); assertTrue("The empty field is valid.", dateField.isEditValid()); dateField.setText(" "); assertTrue("The blank field is valid.", dateField.isEditValid()); } // Integers *************************************************************** public void testDefaultIntegerFieldMapsNullToEmpty() { ValueHolder integerModel = new ValueHolder(); JFormattedTextField integerField = BasicComponentFactory.createIntegerField( integerModel); assertEquals("Default integer field maps null to an empty string.", "", integerField.getText()); integerModel.setValue(42L); setTextAndCommitEdit(integerField, ""); assertEquals("Default integer field maps the empty string to null.", null, integerModel.getValue()); integerModel.setValue(32168); setTextAndCommitEdit(integerField, " "); assertEquals("Default integer field maps blank strings to null.", null, integerModel.getValue()); } public void testCustomIntegerFieldMapsEmptyValueToEmpty() { Integer emptyValue = new Integer(-1); ValueHolder integerModel = new ValueHolder(emptyValue); JFormattedTextField integerField = BasicComponentFactory.createIntegerField( integerModel, emptyValue.intValue()); assertEquals("Custom integer field maps the empty value to an empty string.", "", integerField.getText()); integerModel.setValue(42L); setTextAndCommitEdit(integerField, ""); assertEquals("Custom integer field maps the empty string to the empty value.", emptyValue, integerModel.getValue()); integerModel.setValue(32168); setTextAndCommitEdit(integerField, " "); assertEquals("Custom integer field maps blank strings to the empty value.", emptyValue, integerModel.getValue()); } public void testDefaultIntegerFieldIsValidOnEmptyAndBlank() { JFormattedTextField integerField = BasicComponentFactory.createIntegerField( new ValueHolder()); assertTrue("The empty field is valid.", integerField.isEditValid()); integerField.setText(" "); assertTrue("The blank field is valid.", integerField.isEditValid()); } // Longs ****************************************************************** public void testDefaultLongFieldMapsNullToEmpty() { ValueHolder longModel = new ValueHolder(); JFormattedTextField longField = BasicComponentFactory.createLongField( longModel); assertEquals("Default long field maps null to an empty string.", "", longField.getText()); longModel.setValue(42L); setTextAndCommitEdit(longField, ""); assertEquals("Default long field maps the empty string to null.", null, longModel.getValue()); longModel.setValue(32168); setTextAndCommitEdit(longField, " "); assertEquals("Default long field maps blank strings to null.", null, longModel.getValue()); } public void testCustomLongFieldMapsEmptyValueToEmpty() { Long emptyValue = new Long(-1); ValueHolder longModel = new ValueHolder(emptyValue); JFormattedTextField longField = BasicComponentFactory.createLongField( longModel, emptyValue.longValue()); assertEquals("Custom long field maps the empty value to an empty string.", "", longField.getText()); longModel.setValue(42L); setTextAndCommitEdit(longField, ""); assertEquals("Custom long field maps the empty string to the empty value.", emptyValue, longModel.getValue()); longModel.setValue(32168); setTextAndCommitEdit(longField, " "); assertEquals("Custom long field maps blank strings to the empty value.", emptyValue, longModel.getValue()); } public void testDefaultLongFieldIsValidOnEmptyAndBlank() { JFormattedTextField longField = BasicComponentFactory.createLongField( new ValueHolder()); assertTrue("The empty field is valid.", longField.isEditValid()); longField.setText(" "); assertTrue("The blank field is valid.", longField.isEditValid()); } // Filtering ************************************************************** private static final String TEXT_WITH_NEWLINE = "First line\nsecond line"; private static final String TEXT_WITHOUT_NEWLINE = TEXT_WITH_NEWLINE.replace('\n', ' '); public void testPasswordFieldFiltersNewlines() { ValueModel subject = new ValueHolder(TEXT_WITH_NEWLINE); JPasswordField passwordField = BasicComponentFactory.createPasswordField(subject); assertEquals("The password field's text contains no newlines.", TEXT_WITHOUT_NEWLINE, new String(passwordField.getPassword())); } public void testTextAreaRetainsNewlines() { ValueModel subject = new ValueHolder(TEXT_WITH_NEWLINE); JTextArea textArea = BasicComponentFactory.createTextArea(subject); assertEquals("The text area's text contains newlines.", TEXT_WITH_NEWLINE, textArea.getText()); } public void testTextFieldFiltersNewlines() { ValueModel subject = new ValueHolder(TEXT_WITH_NEWLINE); JTextField textField = BasicComponentFactory.createTextField(subject); assertEquals("The text field's text contains no newlines.", TEXT_WITHOUT_NEWLINE, textField.getText()); } // Helper Code ************************************************************ private void setTextAndCommitEdit( JFormattedTextField formattedTextField, String text) { formattedTextField.setText(text); try { formattedTextField.commitEdit(); } catch (Exception e) { fail("Failed to commit the text '" + text + "'."); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/0000755000175000017500000000000011374522114024702 5ustar twernertwerner././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/ValueHolderWithOldAndNewValueNull.javajgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/ValueHolderWithOldAndNewValueNull.j0000644000175000017500000000471011374522114033511 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.value; import com.jgoodies.binding.value.AbstractValueModel; /** * A ValueModel that reports {@code null} as old and new value * if the value changes. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @see ValueHolderWithNewValueNull * @see ValueHolderWithOldValueNull */ public final class ValueHolderWithOldAndNewValueNull extends AbstractValueModel { private Object value; public ValueHolderWithOldAndNewValueNull(Object initialValue) { value = initialValue; } public ValueHolderWithOldAndNewValueNull(int initialValue) { this(new Integer(initialValue)); } public Object getValue() { return value; } public void setValue(Object newValue) { value = newValue; fireValueChange(null, null); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/CloningValueHolder.java0000644000175000017500000000504611374522114031276 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.value; import com.jgoodies.binding.value.AbstractValueModel; /** * A ValueHolder implementation that creates a new String or Integer * instance every time its value is requested with #getValue. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ */ public final class CloningValueHolder extends AbstractValueModel { private Object value; public CloningValueHolder(Object initialValue) { this.value = initialValue; } public Object getValue() { if (value instanceof String) { return new String((String) value); } else if (value instanceof Integer) { return new Integer(((Integer) value).intValue()); } return value; } public void setValue(Object newValue) { Object oldValue = getValue(); value = newValue; fireValueChange(oldValue, newValue); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/package.html0000644000175000017500000000465511374522114027175 0ustar twernertwerner Consists of test helper classes that implement the ValueModel interface.

Related Documentation

For more information see: @see com.jgoodies.binding.tests @see com.jgoodies.binding.tests.beans @see com.jgoodies.binding.tests.event jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/ToUpperCaseStringHolder.java0000644000175000017500000000433011374522114032264 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.value; import com.jgoodies.binding.value.AbstractValueModel; /** * A String typed ValueModel that modifies set values to uppercase. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ */ public final class ToUpperCaseStringHolder extends AbstractValueModel { private String text; public Object getValue() { return text; } public void setValue(Object newValue) { String newText = ((String) newValue).toUpperCase(); Object oldText = text; text = newText; fireValueChange(oldText, newText); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/RejectingValueModel.java0000644000175000017500000000434011374522114031436 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.value; import com.jgoodies.binding.value.AbstractVetoableValueModel; import com.jgoodies.binding.value.ValueModel; /** * A ValueModel that wraps another ValueModel but rejects all changes * to the underlying (wrapped) model. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ */ public final class RejectingValueModel extends AbstractVetoableValueModel { public RejectingValueModel(ValueModel subject) { super(subject); } @Override public boolean proposedChange(Object oldValue, Object proposedNewValue) { return false; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/ValueHolderWithNewValueNull.java0000644000175000017500000000473311374522114033124 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.value; import com.jgoodies.binding.value.AbstractValueModel; /** * A ValueModel that reports {@code null} as new value if the value changes. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ * * @see ValueHolderWithOldValueNull * @see ValueHolderWithOldAndNewValueNull */ public final class ValueHolderWithNewValueNull extends AbstractValueModel { private Object value; public ValueHolderWithNewValueNull(Object initialValue) { value = initialValue; } public ValueHolderWithNewValueNull(int initialValue) { this(new Integer(initialValue)); } public Object getValue() { return value; } public void setValue(Object newValue) { Object oldValue = getValue(); value = newValue; fireValueChange(oldValue, null); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/value/ValueHolderWithOldValueNull.java0000644000175000017500000000466411374522114033114 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.value; import com.jgoodies.binding.value.AbstractValueModel; /** * A ValueModel that reports {@code null} as old value if the value changes. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ * * @see ValueHolderWithNewValueNull * @see ValueHolderWithOldAndNewValueNull */ public final class ValueHolderWithOldValueNull extends AbstractValueModel { private Object value; public ValueHolderWithOldValueNull(Object initialValue) { value = initialValue; } public ValueHolderWithOldValueNull(int initialValue) { this(new Integer(initialValue)); } public Object getValue() { return value; } public void setValue(Object newValue) { value = newValue; fireValueChange(null, newValue); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/package.html0000644000175000017500000000502211374522114026046 0ustar twernertwerner Contains test cases for the JGoodies Binding classes.

Related Documentation

For more information see: @see com.jgoodies.binding.tests.beans @see com.jgoodies.binding.tests.event @see com.jgoodies.binding.tests.value jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/ConverterFactoryTest.java0000644000175000017500000003051011374522114030567 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; import junit.framework.TestCase; import com.jgoodies.binding.value.AbstractConverter; import com.jgoodies.binding.value.ConverterFactory; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for converters created by the ConverterFactory. * * TODO: add tests for null values in subject. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ */ public final class ConverterFactoryTest extends TestCase { // Constructor Tests ****************************************************** public void testConstructorsRejectNullSubjects() { try { ConverterFactory.createBooleanNegator(null); fail("BooleanNegator should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createBooleanToStringConverter(null, "yes", "no"); fail("BooleanToStringConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createBooleanToStringConverter(null, "yes", "no", "none"); fail("BooleanToStringConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createDoubleToIntegerConverter(null); fail("DoubleToIntegerConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createDoubleToIntegerConverter(null, 100); fail("DoubleToIntegerConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createFloatToIntegerConverter(null); fail("FloatToIntegerConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createFloatToIntegerConverter(null, 100); fail("FloatToIntegerConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createLongToIntegerConverter(null); fail("LongToIntegerConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createLongToIntegerConverter(null, 100); fail("LongToIntegerConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createStringConverter(null, DateFormat.getDateInstance()); fail("StringConverter should reject null subject."); } catch (NullPointerException e) { // The expected behavior } } public void testBooleanToStringConverterRejectsNullTexts() { try { ConverterFactory.createBooleanToStringConverter(new ValueHolder(), null, "no", "none"); fail("BooleanToStringConverter should reject null trueText."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createBooleanToStringConverter(new ValueHolder(), "yes", null, "none"); fail("BooleanToStringConverter should reject null falseText."); } catch (NullPointerException e) { // The expected behavior } try { ConverterFactory.createBooleanToStringConverter(new ValueHolder(), "yes", "no", null); fail("BooleanToStringConverter should reject null falseText."); } catch (NullPointerException e) { // The expected behavior } } public void testBooleanToStringConverterRejectsEqualTexts() { try { ConverterFactory.createBooleanToStringConverter(new ValueHolder(), "yes", "yes", "none"); fail("BooleanToStringConverter should reject equal trueText and falseText."); } catch (IllegalArgumentException e) { // The expected behavior } try { ConverterFactory.createBooleanToStringConverter(new ValueHolder(), "none", "no", "none"); } catch (IllegalArgumentException e) { fail("BooleanToStringConverter should accept equal trueText and nullText."); } try { ConverterFactory.createBooleanToStringConverter(new ValueHolder(), "yes", "none", "none"); } catch (IllegalArgumentException e) { fail("BooleanToStringConverter should accept equal falseText and nullText."); } } // Conversion Tests ******************************************************* public void testBooleanNegator() { ValueModel subject = new ValueHolder(Boolean.TRUE); AbstractConverter c = (AbstractConverter) ConverterFactory.createBooleanNegator(subject); assertInvertable(c, null); assertInvertable(c, Boolean.TRUE); assertInvertable(c, Boolean.FALSE); assertProperConversions(subject, Boolean.TRUE, c, Boolean.FALSE); assertProperConversions(subject, Boolean.FALSE, c, Boolean.TRUE); assertProperConversions(subject, null, c, null); } public void testBooleanToStringConverter() { String trueText = "true"; String falseText = "false"; String nullText = "unknown"; ValueModel subject = new ValueHolder(Boolean.TRUE); AbstractConverter c = (AbstractConverter) ConverterFactory.createBooleanToStringConverter(subject, trueText, falseText, nullText); assertInvertable(c, null); assertInvertable(c, Boolean.TRUE); assertInvertable(c, Boolean.FALSE); assertProperConversions(subject, Boolean.TRUE, c, trueText); assertProperConversions(subject, Boolean.FALSE, c, falseText); assertProperConversions(subject, null, c, nullText); } public void testDoubleConverter() { Double value = new Double(1.0); Double convertedValue = new Double(100); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createDoubleConverter(subject, 100); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } public void testDoubleToIntegerConverter() { Double value = new Double(100.0); Integer convertedValue = new Integer(100); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createDoubleToIntegerConverter(subject, 1); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } public void testFloatConverter() { Float value = new Float(1.0); Float convertedValue = new Float(100); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createFloatConverter(subject, 100); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } public void testFloatToIntegerConverter() { Float value = new Float(100.0f); Integer convertedValue = new Integer(100); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createFloatToIntegerConverter(subject, 1); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } public void testIntegerConverter() { Integer value = new Integer(1); Integer convertedValue = new Integer(100); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createIntegerConverter(subject, 100); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } public void testLongConverter() { Long value = new Long(1L); Long convertedValue = new Long(100L); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createLongConverter(subject, 100); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } public void testLongToIntegerConverter() { Long value = new Long(100); Integer convertedValue = new Integer(100); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createLongToIntegerConverter(subject, 1); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } public void testStringConverter() { Long value = new Long(100); String convertedValue = "100"; DecimalFormat format = new DecimalFormat("#", new DecimalFormatSymbols(Locale.US)); format.setParseIntegerOnly(true); ValueModel subject = new ValueHolder(value); AbstractConverter c = (AbstractConverter) ConverterFactory.createStringConverter(subject, format); assertInvertable(c, value); assertProperConversions(subject, value, c, convertedValue); } // Helper Code ************************************************************ private void assertInvertable(AbstractConverter converter, Object value) { try { converter.setValue(converter.convertFromSubject(value)); } catch (Exception e) { fail("Unexpected exception is thrown: " + e); } } private void assertProperConversions(ValueModel subject, Object value, ValueModel converter, Object convertedValue) { subject.setValue(value); assertEquals("The value returned by converter is not the expected one:", convertedValue, converter.getValue()); converter.setValue(convertedValue); assertEquals("The value returned by subject is not the expected one:", value, subject.getValue()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/BindingsTest.java0000644000175000017500000002050611374522114027031 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import javax.swing.*; import junit.framework.TestCase; import com.jgoodies.binding.adapter.Bindings; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link Bindings}. * * @author Jeanette Winzenburg * @author Karsten Lentzsch * @version $Revision: 1.11 $ */ public final class BindingsTest extends TestCase { private static final String TEXT_WITH_NEWLINE = "First line\nsecond line"; private static final String TEXT_WITHOUT_NEWLINE = TEXT_WITH_NEWLINE.replace('\n', ' '); /** * Checks that the enablement of a JCheckBox is in synch * with the enablement of its model after Binding#bind. */ public void testCheckBoxEnablementInSynchWithModel() { JCheckBox box = new JCheckBox(); box.setEnabled(false); Bindings.bind(box, new ValueHolder()); assertButtonEnablementInSynchWithModel(box); } public void testCheckBoxRetainsProperties() { JCheckBox box = new JCheckBox("Anna"); String actionCommand = "command"; box.setActionCommand(actionCommand); box.setMnemonic('A'); box.setDisplayedMnemonicIndex(3); assertEquals("Old action command", actionCommand, box.getActionCommand()); assertEquals("Old mnemonic", 'A', box.getMnemonic()); assertEquals("Old mnemonic index", 3, box.getDisplayedMnemonicIndex()); Bindings.bind(box, new ValueHolder()); assertEquals("New action command", actionCommand, box.getActionCommand()); assertEquals("New mnemonic", 'A', box.getMnemonic()); assertEquals("New mnemonic index", 3, box.getDisplayedMnemonicIndex()); } /** * Checks that the enablement of a JCheckBoxMenuItem is in synch * with the enablement of its model after Binding#bind. */ public void testCheckBoxItemEnablementInSynchWithModel() { JCheckBoxMenuItem item = new JCheckBoxMenuItem(); item.setEnabled(false); Bindings.bind(item, new ValueHolder()); assertButtonEnablementInSynchWithModel(item); } public void testCheckBoxMenuItemRetainsProperties() { JCheckBoxMenuItem item = new JCheckBoxMenuItem("Anna"); String actionCommand = "command"; item.setActionCommand(actionCommand); item.setMnemonic('A'); item.setDisplayedMnemonicIndex(3); assertEquals("Old action command", actionCommand, item.getActionCommand()); assertEquals("Old mnemonic", 'A', item.getMnemonic()); assertEquals("Old mnemonic index", 3, item.getDisplayedMnemonicIndex()); Bindings.bind(item, new ValueHolder()); assertEquals("New action command", actionCommand, item.getActionCommand()); assertEquals("New mnemonic", 'A', item.getMnemonic()); assertEquals("New mnemonic index", 3, item.getDisplayedMnemonicIndex()); } /** * Checks that the enablement of a JRadioButton is in synch * with the enablement of its model after Binding#bind. */ public void testRadioButtonEnablementInSynchWithModel() { JRadioButton radio = new JRadioButton(); radio.setEnabled(false); Bindings.bind(radio, new ValueHolder(), null); assertButtonEnablementInSynchWithModel(radio); } public void testRadioButtonRetainsProperties() { JRadioButton radio = new JRadioButton("Anna"); String actionCommand = "command"; radio.setActionCommand(actionCommand); radio.setMnemonic('A'); radio.setDisplayedMnemonicIndex(3); assertEquals("Old action command", actionCommand, radio.getActionCommand()); assertEquals("Old mnemonic", 'A', radio.getMnemonic()); assertEquals("Old mnemonic index", 3, radio.getDisplayedMnemonicIndex()); Bindings.bind(radio, new ValueHolder(), Boolean.TRUE); assertEquals("New action command", actionCommand, radio.getActionCommand()); assertEquals("New mnemonic", 'A', radio.getMnemonic()); assertEquals("New mnemonic index", 3, radio.getDisplayedMnemonicIndex()); } /** * Checks that the enablement of a JRadioButtonMenuItem is in synch * with the enablement of its model after Binding#bind. */ public void testRadioButtonMenuItemEnablementInSynchWithModel() { JRadioButtonMenuItem item = new JRadioButtonMenuItem(); item.setEnabled(false); Bindings.bind(item, new ValueHolder(), null); assertButtonEnablementInSynchWithModel(item); } public void testRadioButtonMenuItemRetainsProperties() { JRadioButtonMenuItem item = new JRadioButtonMenuItem("Anna"); String actionCommand = "command"; item.setActionCommand(actionCommand); item.setMnemonic('A'); item.setDisplayedMnemonicIndex(3); assertEquals("Old action command", actionCommand, item.getActionCommand()); assertEquals("Old mnemonic", 'A', item.getMnemonic()); assertEquals("Old mnemonic index", 3, item.getDisplayedMnemonicIndex()); Bindings.bind(item, new ValueHolder(), Boolean.TRUE); assertEquals("New action command", actionCommand, item.getActionCommand()); assertEquals("New mnemonic", 'A', item.getMnemonic()); assertEquals("New mnemonic index", 3, item.getDisplayedMnemonicIndex()); } public void testBoundTextAreaRetainsNewlines() { ValueModel subject = new ValueHolder(TEXT_WITH_NEWLINE); JTextArea textArea = new JTextArea(); Bindings.bind(textArea, subject); assertEquals("The text area's text contains newlines.", TEXT_WITH_NEWLINE, textArea.getText()); } public void testBoundTextFieldFiltersNewlines() { ValueModel subject = new ValueHolder(TEXT_WITH_NEWLINE); JTextField textField = new JTextField(); Bindings.bind(textField, subject); assertEquals("The text field's text contains no newlines.", TEXT_WITHOUT_NEWLINE, textField.getText()); } // Helper Code ************************************************************ /** * Checks that the enablement of the given button is in synch * with the enablement of its model, an instance of ToggleButtonModel. * * @param button the button to check */ private void assertButtonEnablementInSynchWithModel(AbstractButton button) { assertEquals("Enabled state of component and model must be in synch.", button.isEnabled(), button.getModel().isEnabled()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/PreferencesAdapterTest.java0000644000175000017500000002165311374522114031042 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import junit.framework.TestCase; import com.jgoodies.binding.adapter.PreferencesAdapter; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link PreferencesAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ */ public final class PreferencesAdapterTest extends TestCase { private static final String NODE_NAME = "unit-tests"; private Preferences prefs; @Override protected void setUp() { prefs = Preferences.userRoot().node(NODE_NAME); } @Override protected void tearDown() { try { prefs.removeNode(); } catch (BackingStoreException e) { // TODO: handle exception } prefs = null; } // Parameter Tests ****************************************************** public void testConstructorRejectsNullValues() { String key = "constructorNullTest"; prefs.remove(key); try { new PreferencesAdapter(null, key, "default"); fail("PreferencesAdapter(Preferences, String, Object) failed to reject null Preferences."); } catch (NullPointerException ex) { // The expected behavior } try { new PreferencesAdapter(prefs, null, "default"); fail("PreferencesAdapter(Preferences, String, Object) failed to reject a null key."); } catch (NullPointerException ex) { // The expected behavior } try { new PreferencesAdapter(prefs, key, null); fail("PreferencesAdapter(Preferences, String, Object) failed to reject a null default value."); } catch (NullPointerException ex) { // The expected behavior } } public void testConstructorAcceptsKnownDefaultValueTypes() { String key = "constructorIllegalTypeTest"; prefs.remove(key); new PreferencesAdapter(prefs, key, "String"); new PreferencesAdapter(prefs, key, Boolean.TRUE); new PreferencesAdapter(prefs, key, new Double(12.3)); new PreferencesAdapter(prefs, key, new Float(12.3F)); new PreferencesAdapter(prefs, key, new Integer(12)); new PreferencesAdapter(prefs, key, new Long(12L)); } public void testConstructorRejectsIllegalDefaultValueTypes() { String key = "constructorIllegalTypeTest"; prefs.remove(key); try { new PreferencesAdapter(prefs, key, new ValueHolder(1)); fail("PreferencesAdapter(Preferences, String, Object) failed to reject an invalid default value type."); } catch (IllegalArgumentException ex) { // The expected behavior } } // Basic Adapter Features ************************************************* public void testReadWrittenValue() { testReadWrittenValue("Boolean", Boolean.TRUE, Boolean.FALSE); testReadWrittenValue("Double", new Double(1), new Double(2)); testReadWrittenValue("Float", new Float(1), new Float(2)); testReadWrittenValue("Integer", new Integer(1), new Integer(2)); testReadWrittenValue("Long", new Long(1), new Long(2)); testReadWrittenValue("String", "default", "new"); } /** * Checks that the PreferencesAdapter returns the default value * if no value has been stored in the preferences under the given key. * This test first removes the value for the given key - if any. */ public void testReadDefaultValue() { testReadDefaultValue("booleanDefault", Boolean.TRUE); testReadDefaultValue("doubleDefault", new Double(1)); testReadDefaultValue("floatDefault", new Float(1)); testReadDefaultValue("integerDefault", new Integer(1)); testReadDefaultValue("longDefault", new Long(1)); testReadDefaultValue("stringDefault", "default"); } /** * Checks that writing the default value actually writes a value. * Just checks a String-typed value, because the mechanism is all * the same for the different types. */ public void testSettingDefaultValueWrites() { String key = "stringTest"; Object defaultValue = "default"; Object prototypeValue = "prototype"; AbstractValueModel writeAdapter = new PreferencesAdapter(prefs, key, defaultValue); writeAdapter.setValue(defaultValue); AbstractValueModel readAdapter = new PreferencesAdapter(prefs, key, prototypeValue); assertEquals("Failed to return the previously set value.", defaultValue, readAdapter.getValue()); } public void testRejectsWritingNull() { String key = "nullTest"; Object defaultValue = "default"; ValueModel adapter = new PreferencesAdapter(prefs, key, defaultValue); try { adapter.setValue(null); fail("Failed to reject writing null."); } catch (NullPointerException e) { // The expected behavior } } public void testRejectsWritingInconsistentType() { String key = "inconsistentTypesWriteTest"; Object defaultValue = "default"; PreferencesAdapter adapter = new PreferencesAdapter(prefs, key, defaultValue); try { adapter.setInt(3); fail("Failed to reject writing a value type inconsistent with the default value type."); } catch (ClassCastException e) { // The expected behavior } try { adapter.setValue(new Integer(3)); fail("Failed to reject writing a value type inconsistent with the default value type."); } catch (ClassCastException e) { // The expected behavior } } public void testRejectsReadingInconsistentType() { String key = "inconsistentTypesReadTest"; Object defaultValue = "default"; PreferencesAdapter adapter = new PreferencesAdapter(prefs, key, defaultValue); try { adapter.getInt(); fail("Failed to reject writing a value type inconsistent with the default value type."); } catch (ClassCastException e) { // The expected behavior } } // Test Implementations *************************************************** private void testReadWrittenValue(String key, Object defaultValue, Object newValue) { AbstractValueModel adapter = new PreferencesAdapter(prefs, key, defaultValue); adapter.setValue(newValue); assertEquals("Failed to return the previously set value.", newValue, adapter.getValue()); } private void testReadDefaultValue(String key, Object defaultValue) { prefs.remove(key); AbstractValueModel adapter = new PreferencesAdapter(prefs, key, defaultValue); assertEquals("Failed to return the default value.", defaultValue, adapter.getValue()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/ValueChangeTest.java0000644000175000017500000005013211374522114027454 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import junit.framework.TestCase; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.beans.PropertyAdapter; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.BufferedValueModel; import com.jgoodies.binding.value.Trigger; import com.jgoodies.binding.value.ValueModel; /** * Tests old and new values when the bean, value or subject changes in * BeanAdapter, PropertyAdapter, PresentationModel and BufferedValueModel. * * @author Karsten Lentzsch * @version $Revision: 1.15 $ */ public final class ValueChangeTest extends TestCase { private TestBean model1; private TestBean model2; // Setup ****************************************************************** /** * @throws Exception in case of an unexpected problem */ @Override protected void setUp() throws Exception { super.setUp(); model1 = new TestBean(); model2 = new TestBean(); } /** * @throws Exception in case of an unexpected problem */ @Override protected void tearDown() throws Exception { super.tearDown(); model1 = null; model2 = null; } // Public Tests *********************************************************** public void testBeanAdapterBeanChange() { Object[][] pairs = createOldAndNewValuePairs(); for (Object[] element : pairs) { Object bean1Value = element[0]; Object bean2Value = element[1]; testBeanAdapterBeanChange(bean1Value, bean2Value); } } public void testBeanAdapterValueChange() { Object[][] pairs = createOldAndNewValuePairs(); for (Object[] element : pairs) { Object value1 = element[0]; Object value2 = element[1]; testBeanAdapterValueChange(value1, value2); } } public void testPropertyAdapterBeanChange() { Object[][] pairs = createOldAndNewValuePairs(); for (Object[] element : pairs) { Object bean1Value = element[0]; Object bean2Value = element[1]; testPropertyAdapterBeanChange(bean1Value, bean2Value); } } public void testPropertyAdapterValueChange() { Object[][] pairs = createOldAndNewValuePairs(); for (Object[] element : pairs) { Object value1 = element[0]; Object value2 = element[1]; testPropertyAdapterValueChange(value1, value2); } } public void testBufferedValueModelSubjectChange() { Object[][] pairs = createOldAndNewValuePairs(); for (Object[] element : pairs) { Object value1 = element[0]; Object value2 = element[1]; testBufferedValueModelSubjectChange(value1, value2); } } public void testBufferedValueModelValueChange() { Object[][] pairs = createOldAndNewValuePairs(); for (Object[] element : pairs) { Object value1 = element[0]; Object value2 = element[1]; testBufferedValueModelSubjectValueChange(value1, value2); } } // Test Implementations *************************************************** private void testBeanAdapterBeanChange( Object value1, Object value2) { model1.setReadWriteObjectProperty(value1); model2.setReadWriteObjectProperty(value2); BeanAdapter adapter = new BeanAdapter((TestBean)null, true); AbstractValueModel valueModel = adapter.getValueModel("readWriteObjectProperty"); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addPropertyChangeListener("value", changeReport); Object eventsOldValue; Object eventsNewValue; int expectedEventCount = 0; adapter.setBean(model1); boolean firesEventFromNullToModel1 = requiresPropertyChangeEvent(null, value1); if (firesEventFromNullToModel1) { expectedEventCount++; } assertEquals( "Expected event count after null -> model1 (" + null + " -> " + value1 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromNullToModel1) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("null-> model1 change fires proper old value.", null, eventsOldValue); assertEquals("null-> model1 change fires proper new value.", value1, eventsNewValue); } adapter.setBean(model2); boolean firesEventFromModel1ToModel2 = requiresPropertyChangeEvent(value1, value2); if (firesEventFromModel1ToModel2) { expectedEventCount++; } assertEquals( "Expected event count after model1 -> model2 (" + value1 + " -> " + value2 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromModel1ToModel2) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("model1 -> model2 change fires proper old value.", value1, eventsOldValue); assertEquals("model1 -> model2 change fires proper new value.", value2, eventsNewValue); } adapter.setBean(null); boolean firesEventFromModel2ToNull = requiresPropertyChangeEvent(value2, null); if (firesEventFromModel2ToNull) { expectedEventCount++; } assertEquals( "Expected event count after model2 -> null (" + value2 + " -> " + null + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromModel2ToNull) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("model2 -> null change fires proper old value.", value2, eventsOldValue); assertEquals("model2 -> null change fires proper new value.", null, eventsNewValue); } } private void testBeanAdapterValueChange( Object value1, Object value2) { BeanAdapter adapter = new BeanAdapter(model1, true); testValueChange( value1, value2, adapter.getValueModel("readWriteObjectProperty")); } private void testPropertyAdapterBeanChange( Object value1, Object value2) { model1.setReadWriteObjectProperty(value1); model2.setReadWriteObjectProperty(value2); PropertyAdapter adapter = new PropertyAdapter((TestBean)null, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("value", changeReport); Object eventsOldValue; Object eventsNewValue; int expectedEventCount = 0; adapter.setBean(model1); boolean firesEventFromNullToModel1 = requiresPropertyChangeEvent(null, value1); if (firesEventFromNullToModel1) { expectedEventCount++; } assertEquals( "Expected event count after null -> model1 (" + null + " -> " + value2 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromNullToModel1) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("null-> model1 change fires proper old value.", null, eventsOldValue); assertEquals("null-> model1 change fires proper new value.", value1, eventsNewValue); } adapter.setBean(model2); boolean firesEventFromModel1ToModel2 = requiresPropertyChangeEvent(value1, value2); if (firesEventFromModel1ToModel2) { expectedEventCount++; } assertEquals( "Expected event count after model1 -> model2 (" + value1 + " -> " + value2 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromModel1ToModel2) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("model1 -> model2 change fires proper old value.", value1, eventsOldValue); assertEquals("model1 -> model2 change fires proper new value.", value2, eventsNewValue); } adapter.setBean(null); boolean firesEventFromModel2ToNull = requiresPropertyChangeEvent(value2, null); if (firesEventFromModel2ToNull) { expectedEventCount++; } assertEquals( "Expected event count after model2 -> null (" + value2 + " -> " + null + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromModel2ToNull) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("model2 -> null change fires proper old value.", value2, eventsOldValue); assertEquals("model2 -> null change fires proper new value.", null, eventsNewValue); } } private void testPropertyAdapterValueChange( Object value1, Object value2) { testValueChange( value1, value2, new PropertyAdapter(model1, "readWriteObjectProperty", true)); } private void testBufferedValueModelSubjectChange( Object value1, Object value2) { model1.setReadWriteObjectProperty(value1); model2.setReadWriteObjectProperty(value2); PropertyAdapter adapter1 = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyAdapter adapter2 = new PropertyAdapter(model2, "readWriteObjectProperty", true); BufferedValueModel buffer = new BufferedValueModel(null, new Trigger()); PropertyChangeReport changeReport = new PropertyChangeReport(); buffer.addValueChangeListener(changeReport); Object eventsOldValue; Object eventsNewValue; int expectedEventCount = 0; buffer.setSubject(adapter1); boolean firesEventFromNullToModel1 = requiresPropertyChangeEvent(null, value1); if (firesEventFromNullToModel1) { expectedEventCount++; } assertEquals( "Expected event count after null -> adapter1 (" + null + " -> " + value2 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromNullToModel1) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("null-> adapter1 change fires proper old value.", null, eventsOldValue); assertEquals("null-> adapter1 change fires proper new value.", value1, eventsNewValue); } buffer.setSubject(adapter2); boolean firesEventFromModel1ToModel2 = requiresPropertyChangeEvent(value1, value2); if (firesEventFromModel1ToModel2) { expectedEventCount++; } assertEquals( "Expected event count after adapter1 -> adapter2 (" + value1 + " -> " + value2 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromModel1ToModel2) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("adapter1 -> adapter2 change fires proper old value.", value1, eventsOldValue); assertEquals("adapter1 -> adapter2 change fires proper new value.", value2, eventsNewValue); } buffer.setSubject(null); boolean firesEventFromModel2ToNull = requiresPropertyChangeEvent(value2, null); if (firesEventFromModel2ToNull) { expectedEventCount++; } assertEquals( "Expected event count after adapter2 -> null (" + value2 + " -> " + null + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromModel2ToNull) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("adapter2 -> null change fires proper old value.", value2, eventsOldValue); assertEquals("adapter2 -> null change fires proper new value.", null, eventsNewValue); } } private void testBufferedValueModelSubjectValueChange( Object value1, Object value2) { ValueModel valueModel = new PropertyAdapter(model1, "readWriteObjectProperty", true); ValueModel triggerChannel = new Trigger(); testValueChange( value1, value2, new BufferedValueModel(valueModel, triggerChannel)); } private void testValueChange( Object value1, Object value2, ValueModel valueModel) { model1.setReadWriteObjectProperty(null); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addValueChangeListener(changeReport); Object eventsOldValue; Object eventsNewValue; int expectedEventCount = 0; model1.setReadWriteObjectProperty(value1, true); boolean firesEventFromNullToValue1 = requiresPropertyChangeEventWithNull(null, value1); if (firesEventFromNullToValue1) { expectedEventCount++; } assertEquals( "Expected event count after ( null -> " + value2 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromNullToValue1) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("null-> model1 change fires proper old value.", null, eventsOldValue); assertEquals("null-> model1 change fires proper new value.", value1, eventsNewValue); } model1.setReadWriteObjectProperty(value2, true); boolean firesEventFromValue1ToValue2 = requiresPropertyChangeEventWithNull(value1, value2); if (firesEventFromValue1ToValue2) { expectedEventCount++; } assertEquals( "Expected event count after (" + value1 + " -> " + value2 + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromValue1ToValue2) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("model1 -> model2 change fires proper old value.", value1, eventsOldValue); assertEquals("model1 -> model2 change fires proper new value.", value2, eventsNewValue); } model1.setReadWriteObjectProperty(null, true); boolean firesEventFromValue2ToNull = requiresPropertyChangeEventWithNull(value2, null); if (firesEventFromValue2ToNull) { expectedEventCount++; } assertEquals( "Expected event count after (" + value2 + " -> " + null + ").", expectedEventCount, changeReport.eventCount()); if (firesEventFromValue2ToNull) { eventsOldValue = changeReport.lastOldValue(); eventsNewValue = changeReport.lastNewValue(); assertEquals("model2 -> null change fires proper old value.", value2, eventsOldValue); assertEquals("model2 -> null change fires proper new value.", null, eventsNewValue); } } // Helper Code ************************************************************ private Object[][] createOldAndNewValuePairs() { String sameValue = "same value"; List list1a = Collections.emptyList(); List list1b = new LinkedList(); List list2a = new ArrayList(); list2a.add("one"); List list2b = new ArrayList(list2a); List list3 = new ArrayList(list2a); list3.add("two"); return new Object[][] { {null, null}, {null, "value"}, {"value", null}, {sameValue, sameValue}, {"equal value", new String("equal value")}, {"value1", "value2"}, {new Float(1), new Float(1)}, {new Float(1), new Float(2)}, {Boolean.TRUE, new Boolean(true)}, {list1a, list1a}, {list1a, list1b}, {list1a, list2a}, {list2a, list2a}, {list2a, list2b}, {list2a, list3}, }; } /** * Checks and answers whether changing a property from value1 to value2 * requires to send a PropertyChangeEvent. A future version may be * placed in class Model or ExtendedPropertyChangeSupport; it may test * for known core types that can be compared via #equals, not ==. */ private boolean requiresPropertyChangeEvent(Object value1, Object value2) { return value1 != value2; } /** * Checks and answers whether changing a property from value1 to value2 * requires to send a PropertyChangeEvent. A future version may be * placed in class Model or ExtendedPropertyChangeSupport; it may test * for known core types that can be compared via #equals, not ==. */ private boolean requiresPropertyChangeEventWithNull(Object value1, Object value2) { return (value1 != value2) || (value1 == null && value2 == null); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/TextComponentConnectorTest.java0000644000175000017500000001514411374522114031760 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.lang.reflect.InvocationTargetException; import javax.swing.JTextField; import javax.swing.SwingUtilities; import junit.framework.TestCase; import com.jgoodies.binding.adapter.Bindings; import com.jgoodies.binding.adapter.TextComponentConnector; import com.jgoodies.binding.tests.value.ToUpperCaseStringHolder; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link TextComponentConnector}. * * @author Karsten Lentzsch * @version $Revision: 1.7 $ */ public final class TextComponentConnectorTest extends TestCase { // Constructor Tests ****************************************************** public void testConstructorRejectsNullParameters() { ValueModel subject = new ValueHolder(); JTextField textField = new JTextField(); try { new TextComponentConnector(null, textField); fail("Constructor failed to reject null subject."); } catch (NullPointerException ex) { // The expected behavior } try { new TextComponentConnector(subject, (JTextField) null); fail("Constructor failed to reject null text field."); } catch (NullPointerException ex) { // The expected behavior } } public void testConstructorLeavesSubjectAndTextFieldUnchanged() { String subjectText = "subjectText"; String fieldText = "fieldText"; ValueModel subject = new ValueHolder(subjectText); JTextField textField = new JTextField(fieldText); new TextComponentConnector(subject, textField); assertEquals("The constructor must not change the subject value.", subjectText, subject.getValue()); assertEquals("The constructor must not change the text field text.", fieldText, textField.getText()); } public void testAcceptsMultipleReleases() { String subjectText = "subjectText"; String fieldText = "fieldText"; ValueModel subject = new ValueHolder(subjectText); JTextField textField = new JTextField(fieldText); TextComponentConnector connector = new TextComponentConnector(subject, textField); connector.release(); connector.release(); } public void testBindingsUpdatesTextFieldText() { String subjectText = "subjectText"; String fieldText = "fieldText"; ValueModel subject = new ValueHolder(subjectText); JTextField textField = new JTextField(fieldText); Bindings.bind(textField, subject); assertEquals("The #bind method must not change the subject value.", subjectText, subject.getValue()); assertEquals("The #bind method must change the text field text.", subjectText, textField.getText()); } // Synchronization ******************************************************** public void testSubjectChangeUpdatesTextComponent() { String subjectText = "subjectText"; String fieldText = "fieldText"; ValueModel subject = new ValueHolder(subjectText); JTextField textField = new JTextField(fieldText); Bindings.bind(textField, subject); subject.setValue("newSubjectText"); assertEquals( "Failed to update the text component.", subject.getValue(), textField.getText()); } public void testTextComponentChangeUpdatesSubject() { String subjectText = "subjectText"; String fieldText = "fieldText"; ValueModel subject = new ValueHolder(subjectText); JTextField textField = new JTextField(fieldText); Bindings.bind(textField, subject); textField.setText("newFieldText"); assertEquals( "Failed to update the text component.", textField.getText(), subject.getValue()); } public void testTextComponentChangeHonorsSubjectModifications() { String subjectText = "subjectText"; String fieldText = "fieldText"; final ValueModel subject = new ToUpperCaseStringHolder(); subject.setValue(subjectText); final JTextField textField = new JTextField(fieldText); Bindings.bind(textField, subject); textField.setText("newFieldText"); try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { assertEquals( "Failed to honor the subject modifications.", subject.getValue(), textField.getText()); } }); } catch (InterruptedException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/ValueHolderTest.java0000644000175000017500000001066511374522114027513 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import junit.framework.TestCase; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * Tests old and new values when the bean, value or subject changes in * BeanAdapter, PropertyAdapter, PresentationModel and BufferedValueModel. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ */ public final class ValueHolderTest extends TestCase { // Public Tests *********************************************************** public void testEquityTestingHolderSendsProperEvents() { ValueHolder holder = new ValueHolder(); Object obj1 = new Integer(1); Object obj2a = new Integer(2); Object obj2b = new Integer(2); testValueChangeSendsProperEvent(holder, null, obj1, true); testValueChangeSendsProperEvent(holder, obj1, null, true); testValueChangeSendsProperEvent(holder, obj1, obj1, false); testValueChangeSendsProperEvent(holder, obj1, obj2a, true); testValueChangeSendsProperEvent(holder, obj2a, obj2b, false); // equals testValueChangeSendsProperEvent(holder, null, null, false); } public void testIdentityTestingHolderSendsProperEvents() { ValueHolder holder = new ValueHolder(null, true); Object obj1 = new Integer(1); Object obj2a = new Integer(2); Object obj2b = new Integer(2); testValueChangeSendsProperEvent(holder, null, obj1, true); testValueChangeSendsProperEvent(holder, obj1, null, true); testValueChangeSendsProperEvent(holder, obj1, obj1, false); testValueChangeSendsProperEvent(holder, obj1, obj2a, true); testValueChangeSendsProperEvent(holder, obj2a, obj2b, true); // != testValueChangeSendsProperEvent(holder, null, null, false); } // Test Implementations *************************************************** private void testValueChangeSendsProperEvent( ValueModel valueModel, Object oldValue, Object newValue, boolean eventExpected) { valueModel.setValue(oldValue); PropertyChangeReport changeReport = new PropertyChangeReport(); valueModel.addValueChangeListener(changeReport); int expectedEventCount = eventExpected ? 1 : 0; valueModel.setValue(newValue); assertEquals( "Expected event count after ( " + oldValue + " -> " + newValue + ").", expectedEventCount, changeReport.eventCount()); if (eventExpected) { assertEquals("Event's old value.", oldValue, changeReport.lastOldValue()); assertEquals("Event's new value.", newValue, changeReport.lastNewValue()); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/BoundedRangeAdapterTest.java0000644000175000017500000001142611374522114031133 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import javax.swing.BoundedRangeModel; import junit.framework.TestCase; import com.jgoodies.binding.adapter.BoundedRangeAdapter; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link BoundedRangeAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ */ public final class BoundedRangeAdapterTest extends TestCase { // Constructor Tests ****************************************************** public void testConstructorRejectsNullSubject() { try { new BoundedRangeAdapter(null, 1, 0, 10); fail("BoundedRangeAdapter constructor failed to reject a null subject."); } catch (NullPointerException ex) { // The expected behavior } } public void testConstructorAcceptsNullSubjectValue() { new BoundedRangeAdapter(new ValueHolder(), 1, 0, 10); } public void testConstructorRejectsIllegalArguments() { ValueModel subject = new ValueHolder(1); try { new BoundedRangeAdapter(subject, 1, 0, -1); fail("Constructor must reject if min > max."); } catch (IllegalArgumentException ex) { // The expected behavior } try { new BoundedRangeAdapter(subject, 1, 2, 0); fail("Constructor must reject if initial value < min."); } catch (IllegalArgumentException ex) { // The expected behavior } try { new BoundedRangeAdapter(subject, 1, 0, 1); fail("Constructor must reject if initial value + extent > max."); } catch (IllegalArgumentException ex) { // The expected behavior } try { new BoundedRangeAdapter(subject, -1, 0, 10); fail("Constructor must reject if extent < 0."); } catch (IllegalArgumentException ex) { // The expected behavior } } // Basic Adapter Features ************************************************* public void testAcceptsNullSubjectValue() { BoundedRangeModel adapter = new BoundedRangeAdapter(new ValueHolder(), 1, 0, 10); adapter.getValue(); } public void testReadsSubjectValue() { ValueModel subject = new ValueHolder(1); BoundedRangeModel adapter = new BoundedRangeAdapter(subject, 1, 0, 10); assertEquals("The adapter returns the initial subject value.", subject.getValue(), new Integer(adapter.getValue())); subject.setValue(new Integer(2)); assertEquals("The adapter reflects the subject value change.", subject.getValue(), new Integer(adapter.getValue())); } public void testWritesSubjectValue() { ValueModel subject = new ValueHolder(1); BoundedRangeModel adapter = new BoundedRangeAdapter(subject, 1, 0, 10); adapter.setValue(2); assertEquals("The subject returns the new adapter value.", subject.getValue(), new Integer(adapter.getValue())); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/PropertyConnectorTest.java0000644000175000017500000003773011374522114031002 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import junit.framework.TestCase; import com.jgoodies.binding.beans.PropertyConnector; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.tests.value.ToUpperCaseStringHolder; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link PropertyConnector}. * * @author Karsten Lentzsch * @version $Revision: 1.19 $ */ public final class PropertyConnectorTest extends TestCase { private PropertyConnector createDefaultConnector(TestBean bean1, TestBean bean2) { return PropertyConnector.connect( bean1, "readWriteObjectProperty", bean2, "readWriteObjectProperty"); } private PropertyConnector createDefaultConnector(TestBean bean1, Object initialValue1, TestBean bean2, Object initialValue2) { bean1.setReadWriteObjectProperty(initialValue1); bean2.setReadWriteObjectProperty(initialValue2); return createDefaultConnector(bean1, bean2); } // Constructor Tests ****************************************************** public void testConstructorRejectsNullParameters() { TestBean bean1; TestBean bean2; bean1 = new TestBean(); bean2 = new TestBean(); try { PropertyConnector.connect(null, "readWriteObjectProperty", bean2, "readWriteObjectProperty"); fail("Constructor failed to reject bean1==null."); } catch (NullPointerException ex) { // The expected behavior } bean1 = new TestBean(); bean2 = new TestBean(); try { PropertyConnector.connect(bean1, null, bean2, "readWriteObjectProperty"); fail("Constructor failed to reject property1Name==null."); } catch (NullPointerException ex) { // The expected behavior } bean1 = new TestBean(); bean2 = new TestBean(); try { PropertyConnector.connect(bean1, "readWriteObjectProperty", null, "readWriteObjectProperty"); fail("Constructor failed to reject bean2==null."); } catch (NullPointerException ex) { // The expected behavior } bean1 = new TestBean(); bean2 = new TestBean(); try { PropertyConnector.connect(bean1, "readWriteObjectProperty", bean2, null); fail("Constructor failed to reject property2Name==null."); } catch (NullPointerException ex) { // The expected behavior } } public void testConstructorRejectsToConnectTheSameProperty() { TestBean bean = new TestBean(); try { PropertyConnector.connect(bean, "readWriteObjectProperty", bean, "readWriteObjectProperty"); fail("Constructor failed to reject bean1==bean2 && property1Name.equals(property2Name)."); } catch (IllegalArgumentException ex) { // The expected behavior } } public void testConstructorRejectsWriteOnlyProperty1() { TestBean bean = new TestBean(); try { PropertyConnector.connect(bean, "writeOnlyObjectProperty", bean, "readWriteObjectProperty"); fail("Constructor failed to reject the write only property1."); } catch (IllegalArgumentException ex) { // The expected behavior } } public void testConstructorRejectsWriteOnlyProperty2() { TestBean bean = new TestBean(); try { PropertyConnector.connect(bean, "readWriteObjectProperty", bean, "writeOnlyObjectProperty"); fail("Constructor failed to reject the write only property2."); } catch (IllegalArgumentException ex) { // The expected behavior } } public void testConstructorRejectsReadOnlyBeans() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); try { PropertyConnector.connect(bean1, "readOnlyObjectProperty", bean2, "readOnlyObjectProperty"); fail("Constructor failed to reject read-only bean properties."); } catch (IllegalArgumentException ex) { // The expected behavior } } public void testConstructorLeavesValuesUnchanged() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); Object initialValue1 = "initialValue1"; Object initialValue2 = "initialValue2"; createDefaultConnector(bean1, initialValue1, bean2, initialValue2); assertEquals("The constructor must not change the property1.", initialValue1, bean1.getReadWriteObjectProperty()); assertEquals("The constructor must not change the property2.", initialValue2, bean2.getReadWriteObjectProperty()); } public void testAcceptsMultipleReleases() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); Object initialValue1 = "initialValue1"; Object initialValue2 = "initialValue2"; PropertyConnector connector = createDefaultConnector(bean1, initialValue1, bean2, initialValue2); connector.release(); connector.release(); } // Synchronization ******************************************************** public void testUpdateProperty1() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); Object initialValue2 = "newValue2"; PropertyConnector connector = createDefaultConnector(bean1, "value1", bean2, initialValue2); connector.updateProperty1(); assertEquals( "#updateProperty1 failed to update property1.", bean1.getReadWriteObjectProperty(), bean2.getReadWriteObjectProperty()); } public void testUpdateProperty2() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); Object initialValue1 = "newValue1"; PropertyConnector connector = createDefaultConnector(bean1, initialValue1, bean2, "value2"); connector.updateProperty2(); assertEquals( "#updateProperty2 failed to update property2.", bean1.getReadWriteObjectProperty(), bean2.getReadWriteObjectProperty()); } public void testProperty1ChangeUpdatesProperty2() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); createDefaultConnector(bean1, "value1", bean2, "value2"); bean1.setReadWriteObjectProperty("newValue1"); assertEquals( "Failed to update property2 after a named change.", bean1.getReadWriteObjectProperty(), bean2.getReadWriteObjectProperty()); } public void testProperty2ChangeUpdatesProperty1() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); createDefaultConnector(bean1, "value1", bean2, "value2"); bean2.setReadWriteObjectProperty("newValue1"); assertEquals( "Failed to update property1 after a named change.", bean1.getReadWriteObjectProperty(), bean2.getReadWriteObjectProperty()); } public void testProperty1AnonymousChangeUpdatesProperty2() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); createDefaultConnector(bean1, "value1", bean2, "value2"); bean1.setReadWriteObjectProperties("newValue1", true, 42); assertEquals( "Failed to update property2 after an unnamed change.", bean1.getReadWriteObjectProperty(), bean2.getReadWriteObjectProperty()); } public void testProperty2AnonymousChangeUpdatesProperty1() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); createDefaultConnector(bean1, "value1", bean2, "value2"); bean2.setReadWriteObjectProperties("newValue1", true, 42); assertEquals( "Failed to update property1 after an unnamed change.", bean1.getReadWriteObjectProperty(), bean2.getReadWriteObjectProperty()); } // Avoid Unnecessary Events *********************************************** public void testAvoidUnnecessaryChangeEvents() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); createDefaultConnector(bean1, "value1", bean2, null); PropertyChangeReport report = new PropertyChangeReport(); bean2.addPropertyChangeListener("readWriteObjectProperty", report); assertEquals( "No changes.", 0, report.eventCount()); bean1.setReadWriteObjectProperty(null); assertEquals( "bean2 remains the same", 0, report.eventCount()); bean1.setReadWriteObjectProperty("newValue1"); assertEquals( "bean2 updated", 1, report.eventCount()); } // Events that lack the new value ***************************************** public void testProperty1ChangeWithNullEventNewValueUpdatesProperty2() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); PropertyConnector.connect( bean1, "nullNewValueProperty", bean2, "readWriteObjectProperty"); bean1.setNullNewValueProperty("newValue1"); assertEquals( "Failed to update property2 after a change with event that has a null new value.", bean1.getNullNewValueProperty(), bean2.getReadWriteObjectProperty()); } public void testProperty2ChangeWithNullEventNewValueUpdatesProperty1() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); PropertyConnector.connect( bean1, "readWriteObjectProperty", bean2, "nullNewValueProperty"); bean2.setNullNewValueProperty("newValue1"); assertEquals( "Failed to update property1 after a change with event that has a null new value.", bean1.getReadWriteObjectProperty(), bean2.getNullNewValueProperty()); } // One-Way Synchronization ************************************************ public void testConnectReadWriteProperty1WithReadOnlyProperty2() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); bean1.setReadWriteObjectProperty("initalValue1"); PropertyConnector.connect( bean1, "readWriteObjectProperty", bean2, "readOnlyObjectProperty"); testConnectReadWriteWithReadOnlyProperty(bean1, bean2); } public void testConnectReadOnlyProperty1WithReadWriteProperty2() { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); bean1.setReadWriteObjectProperty("initalValue1"); PropertyConnector.connect( bean1, "readOnlyObjectProperty", bean2, "readWriteObjectProperty"); testConnectReadWriteWithReadOnlyProperty(bean2, bean1); } public void testUpdateHonorsTargetModifications() { ValueModel bean1 = new ValueHolder("initalValue"); ValueModel bean2 = new ToUpperCaseStringHolder(); PropertyConnector.connect( bean1, "value", bean2, "value"); String newValue = "newValue"; String newValueUpperCase = newValue.toUpperCase(); bean1.setValue("newValue"); assertEquals("Target value is uppercase", newValueUpperCase, bean2.getValue()); assertEquals("Source value is uppercase too", newValueUpperCase, bean1.getValue()); } public void testConnectReadOnlyWithModifyingTarget() { TestBean bean1 = new TestBean(); ValueModel bean2 = new ToUpperCaseStringHolder(); bean1.readOnlyObjectProperty = "initialValue"; PropertyConnector.connect( bean1, "readOnlyObjectProperty", bean2, "value"); // Update property1 if the read-only property2 changes. String newValue = "newValue"; bean1.fireChangeOnReadOnlyObjectProperty(newValue); assertEquals( "Bean2 has the read-only in upper case.", newValue.toUpperCase(), bean2.getValue()); } // Helper Code ************************************************************ private void testConnectReadWriteWithReadOnlyProperty( TestBean beanWithReadWriteProperty, TestBean beanWithReadOnlyProperty) { Object initialValue2 = "initialValue2"; beanWithReadOnlyProperty.readOnlyObjectProperty = initialValue2; // Ignore updates of property1. beanWithReadWriteProperty.setReadWriteObjectProperty("newValue1"); assertEquals( "The connector must not update property2.", initialValue2, beanWithReadOnlyProperty.getReadOnlyObjectProperty()); // Update property1 if the read-only property2 changes. Object newValue2 = "newValue2"; beanWithReadOnlyProperty.fireChangeOnReadOnlyObjectProperty(newValue2); assertEquals( "Bean2 has a new value for the read-only property2.", newValue2, beanWithReadOnlyProperty.getReadOnlyObjectProperty()); assertEquals( "The connector must update property1.", newValue2, beanWithReadWriteProperty.getReadWriteObjectProperty()); // Ignore subsequent updates of property1. beanWithReadWriteProperty.setReadWriteObjectProperty("newValue1b"); assertEquals( "The connector must not update property2.", newValue2, beanWithReadOnlyProperty.getReadOnlyObjectProperty()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/TriggerTest.java0000644000175000017500000001557311374522114026707 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import junit.framework.TestCase; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.value.Trigger; /** * A test case for class {@link Trigger}. * * * @author Karsten Lentzsch * @version $Revision: 1.13 $ */ public final class TriggerTest extends TestCase { /** * Verifies that the trigger accepts Boolean values and null. */ public void testAcceptsBoolean() { Trigger trigger = new Trigger(); trigger.setValue(true); trigger.setValue(false); trigger.setValue(null); trigger.setValue(Boolean.TRUE); trigger.setValue(Boolean.FALSE); } /** * Checks that Trigger.setValue rejects non-Boolean values with an * IllegalArgumentException. */ public void testRejectsNonBooleans() { Trigger trigger = new Trigger(); try { trigger.setValue("Wow"); fail("The Trigger must reject non-Boolean values, here: String."); } catch (IllegalArgumentException e) { // The expected behavior } try { trigger.setValue(1967); fail("The Trigger must reject non-Boolean values, here: Integer."); } catch (IllegalArgumentException e) { // The expected behavior } } /** * Checks that #triggerCommit fires a change event with new * value = Boolean.TRUE. */ public void testTriggerCommit() { Trigger trigger = new Trigger(); PropertyChangeReport changeReport = new PropertyChangeReport(); trigger.setValue(null); trigger.addValueChangeListener(changeReport); trigger.triggerCommit(); assertEquals( "Commit from null fires a single event.", changeReport.eventCount(), 1); assertEquals( "Event new value equals Boolean.TRUE.", changeReport.lastNewValue(), Boolean.TRUE); assertEquals( "Trigger value equals Boolean.TRUE.", trigger.getValue(), Boolean.TRUE); trigger.setValue(true); assertEquals("Nothing changed.", changeReport.eventCount(), 1); trigger.triggerCommit(); assertEquals( "Commit from TRUE fires two events.", changeReport.eventCount(), 3); assertEquals( "Event new value equals Boolean.TRUE.", changeReport.lastNewValue(), Boolean.TRUE); assertEquals( "Trigger value equals Boolean.TRUE.", trigger.getValue(), Boolean.TRUE); trigger.setValue(false); assertEquals( "Changed from true to false.", changeReport.eventCount(), 4); trigger.triggerCommit(); assertEquals( "Commit from FALSE fires a single events.", changeReport.eventCount(), 5); assertEquals( "Event new value equals Boolean.TRUE.", changeReport.lastNewValue(), Boolean.TRUE); assertEquals( "Trigger value equals Boolean.TRUE.", trigger.getValue(), Boolean.TRUE); } /** * Checks that #triggerFlush fires a change event with new * value = Boolean.FALSE. */ public void testTriggerFlush() { Trigger trigger = new Trigger(); PropertyChangeReport changeReport = new PropertyChangeReport(); trigger.setValue(null); trigger.addValueChangeListener(changeReport); trigger.triggerFlush(); assertEquals( "Flush from null fires a single event.", changeReport.eventCount(), 1); assertEquals( "Event new value equals Boolean.FALSE.", changeReport.lastNewValue(), Boolean.FALSE); assertEquals( "Trigger value equals Boolean.FALSE.", trigger.getValue(), Boolean.FALSE); trigger.setValue(true); assertEquals( "Changed from false to true.", changeReport.eventCount(), 2); trigger.triggerFlush(); assertEquals( "Flush from TRUE fires a single event.", changeReport.eventCount(), 3); assertEquals( "Event new value equals Boolean.FALSE.", changeReport.lastNewValue(), Boolean.FALSE); assertEquals( "Trigger value equals Boolean.FALSE.", trigger.getValue(), Boolean.FALSE); trigger.setValue(false); assertEquals("Nothing changed.", changeReport.eventCount(), 3); trigger.triggerFlush(); assertEquals( "Flush from FALSE fires two events.", changeReport.eventCount(), 5); assertEquals( "Event new value equals Boolean.FALSE.", changeReport.lastNewValue(), Boolean.FALSE); assertEquals( "Trigger value equals Boolean.FALSE.", trigger.getValue(), Boolean.FALSE); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/AbstractTableAdapterTest.java0000644000175000017500000000771011374522114031312 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import javax.swing.ListModel; import junit.framework.TestCase; import com.jgoodies.binding.adapter.AbstractTableAdapter; import com.jgoodies.common.collect.ArrayListModel; /** * A test case for class {@link AbstractTableAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ */ public final class AbstractTableAdapterTest extends TestCase { private static final String[] COLUMN_NAMES = {"Title", "Artist"}; // Constructor Tests ****************************************************** public void testConstructorRejectsNullListModel() { try { new ExampleTableModel(null); } catch (NullPointerException e) { // The expected behavior. } try { new ExampleTableModel(null, COLUMN_NAMES); } catch (NullPointerException e) { // The expected behavior. } } public void testConstructorAcceptsNullColumnNames() { try { new ExampleTableModel(new ArrayListModel()); } catch (NullPointerException e) { fail("AbstractTableAdapter(ListModel) is correct if the ListModel is not null."); } try { new ExampleTableModel(new ArrayListModel(), null); } catch (NullPointerException e) { fail("AbstractTableAdapter(ListModel, String[]) must accept a null columnName argument."); } } // Helper Code ************************************************************ /** * An example TableModel that presents an Album's title and artist. */ private static final class ExampleTableModel extends AbstractTableAdapter { private ExampleTableModel(ListModel listModel) { super(listModel); } private ExampleTableModel(ListModel listModel, String[] columnNames) { super(listModel, columnNames); } public Object getValueAt(int rowIndex, int columnIndex) { Object row = getRow(rowIndex); switch (columnIndex) { case 0 : return "Title of " + row; case 1 : return "Artist of " + row; default : throw new IllegalStateException("Unknown column"); } } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/PresentationModelTest.java0000644000175000017500000003716011374522114030734 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.beans.PropertyVetoException; import junit.framework.TestCase; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.tests.beans.EquityTestBean; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.value.BufferedValueModel; import com.jgoodies.binding.value.Trigger; import com.jgoodies.binding.value.ValueHolder; /** * A test case for class {@link com.jgoodies.binding.PresentationModel}. * * @author Karsten Lentzsch * @version $Revision: 1.15 $ */ public final class PresentationModelTest extends TestCase { // Life Cycle ************************************************************* public void testAcceptsMultipleReleases() { PresentationModel pm = new PresentationModel(new TestBean()); pm.release(); pm.release(); } // Null As Property Name ************************************************** public void testRejectNullPropertyName() { testRejectNullPropertyName(null); testRejectNullPropertyName(new TestBean()); } private void testRejectNullPropertyName(TestBean bean) { PresentationModel model = new PresentationModel(bean); try { model.getValue(null); fail("#getValue(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.setValue(null, null); fail("#setValue(null, Object) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.setVetoableValue(null, null); fail("#setVetoableValue(null, Object) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } catch (PropertyVetoException e) { fail("An NPE should be thrown."); } try { model.getBufferedValue(null); fail("#getBufferedValue(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.setBufferedValue(null, null); fail("#setBufferedValue(null, Object) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.getModel(null); fail("#getModel(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.getModel(null, "readA", "writeA"); fail("#getModel(null, String, String) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.getComponentModel(null); fail("#getComponentModel(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.getBufferedModel(null); fail("#getBufferedModel(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.getBufferedModel(null, "readA", "writeA"); fail("#getBufferedModel(null, String, String) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } try { model.getBufferedComponentModel(null); fail("#getBufferedComponentModel(null) should throw an NPE."); } catch (NullPointerException e) { // The expected behavior } } // ************************************************************************ /** * Verifies that the factory method vends the same instance of the * buffered model if called multiple times. */ public void testVendsSameModel() { PresentationModel model = new PresentationModel(new TestBean()); Object model1 = model.getModel("readWriteObjectProperty"); Object model2 = model.getModel("readWriteObjectProperty"); assertSame("The factory method vends the same instance.", model1, model2); } /** * Verifies that the factory method vends the same instance of the * buffered model if called multiple times. */ public void testVendsSameBufferedModel() { PresentationModel model = new PresentationModel(new TestBean()); Object model1 = model.getBufferedModel("readWriteObjectProperty"); Object model2 = model.getBufferedModel("readWriteObjectProperty"); assertSame("The factory method vends the same instance.", model1, model2); } /** * Verifies that the PresentationModel rejects attempts to get an adapting * ValueModel by means of #getModel with different * property accessor names. In other words, for each bean property * API users must use either {@link PresentationModel#getModel(String)} or * {@link PresentationModel#getModel(String, String, String)}, not both. * And all calls to the latter method must use the same getter and setter * names for the same property name.

* * This test invokes both methods for the same property name with different * getter and/or setter names and expects that the second call is rejected. * The PresentationModel is created without a bean set, to avoid that the * underlying BeanAdapter checks for a valid property. */ public void testRejectsGetModelWithDifferentAccessors() { String failureText = "The PresentationModel must reject attempts " + "to get a ValueModel for the same property " + "with different accessor names."; String propertyName = "property"; String getterName1 = "getter1"; String getterName2 = "getter2"; String setterName1 = "setter1"; String setterName2 = "setter2"; PresentationModel model1 = new PresentationModel(null); model1.getModel(propertyName); try { model1.getModel(propertyName, getterName1, setterName1); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } PresentationModel model2 = new PresentationModel(null); model2.getModel(propertyName, getterName1, setterName1); try { model2.getModel(propertyName); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } PresentationModel model3 = new PresentationModel(null); model3.getModel(propertyName, getterName1, setterName1); try { model3.getModel(propertyName, getterName2, setterName1); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } PresentationModel model4 = new PresentationModel(null); model4.getModel(propertyName, getterName1, setterName1); try { model4.getModel(propertyName, getterName1, setterName2); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } } /** * Verifies that the PresentationModel rejects attempts to get a buffered * adapting ValueModel by means of #getBufferedModel with * different accessor names. In other words, for each bean property API * users must use either {@link PresentationModel#getBufferedModel(String)} * or {@link PresentationModel#getBufferedModel(String, String, String)}, * not both. And all calls to the latter method must use the same getter * and setter names for the same property name.

* * This test invokes both methods for the same property name with different * getter and/or setter names and expects that the second call is rejected. * The PresentationModel is created without a bean set, to avoid that the * underlying BeanAdapter checks for a valid property. */ public void testRejectsGetBufferedModelWithDifferentAccessors() { String failureText = "The PresentationModel must reject attempts " + "to get a buffered ValueModel for the same property " + "with different accessor names."; String propertyName = "property"; String getterName1 = "getter1"; String getterName2 = "getter2"; String setterName1 = "setter1"; String setterName2 = "setter2"; PresentationModel model1 = new PresentationModel(null); model1.getBufferedModel(propertyName); try { model1.getBufferedModel(propertyName, getterName1, setterName1); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } PresentationModel model2 = new PresentationModel(null); model2.getBufferedModel(propertyName, getterName1, setterName1); try { model2.getBufferedModel(propertyName); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } PresentationModel model3 = new PresentationModel(null); model3.getBufferedModel(propertyName, getterName1, setterName1); try { model3.getBufferedModel(propertyName, getterName2, setterName1); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } PresentationModel model4 = new PresentationModel(null); model4.getBufferedModel(propertyName, getterName1, setterName1); try { model4.getBufferedModel(propertyName, getterName1, setterName2); fail(failureText); } catch (IllegalArgumentException e) { // The expected result. } } public void testSetTriggerChannelUpdatesExistingBufferedValueModels() { Object value1 = "value1"; Object value2 = "value2"; Object value3 = "value3"; Object value4 = "value4"; TestBean bean = new TestBean(); bean.setReadWriteObjectProperty(value1); Trigger trigger1 = new Trigger(); Trigger trigger2 = new Trigger(); PresentationModel model = new PresentationModel(bean, trigger1); BufferedValueModel buffer = model.getBufferedModel("readWriteObjectProperty"); buffer.setValue(value2); trigger1.triggerCommit(); assertEquals("Before the trigger change Trigger1 commits buffered values.", value2, bean.getReadWriteObjectProperty()); buffer.setValue(value3); trigger2.triggerCommit(); assertEquals("Before the trigger change Trigger2 does not affect the buffer.", value2, bean.getReadWriteObjectProperty()); model.setTriggerChannel(trigger2); trigger1.triggerCommit(); assertEquals("After the trigger change Trigger1 shall not affect the buffered value anymore.", value2, bean.getReadWriteObjectProperty()); buffer.setValue(value4); trigger2.triggerCommit(); assertEquals("After the trigger change Trigger2 shall commit buffered values.", value4, bean.getReadWriteObjectProperty()); } // Testing Bean Changes *************************************************** public void testBeanChangeFiresThreeBeanEvents() { TestBean bean = new TestBean(); PresentationModel model = new PresentationModel((TestBean) null); PropertyChangeReport changeReport = new PropertyChangeReport(); model.addPropertyChangeListener(changeReport); model.setBean(bean); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport.eventCount()); } public void testEqualBeanChangeFiresThreeBeanEvents() { EquityTestBean bean1 = new EquityTestBean("bean"); EquityTestBean bean2 = new EquityTestBean("bean"); assertEquals("The two test beans are equal.", bean1, bean2); assertNotSame("The two test beans are not the same.", bean1, bean2); PresentationModel model1 = new PresentationModel(bean1); PropertyChangeReport beanChannelValueChangeReport = new PropertyChangeReport(); model1.getBeanChannel().addValueChangeListener(beanChannelValueChangeReport); PropertyChangeReport changeReport1 = new PropertyChangeReport(); model1.addPropertyChangeListener(changeReport1); model1.setBean(bean2); assertEquals("Changing the bean fires a change event in the bean channel.", 1, beanChannelValueChangeReport.eventCount()); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport1.eventCount()); PresentationModel model2 = new PresentationModel(new ValueHolder(null, true)); model2.setBean(bean1); PropertyChangeReport changeReport2 = new PropertyChangeReport(); model2.addPropertyChangeListener(changeReport2); model2.setBean(bean2); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport2.eventCount()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/IndirectPropertyChangeSupportTest.java0000644000175000017500000003004011374522114033277 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.beans.PropertyChangeListener; import junit.framework.TestCase; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.beans.IndirectPropertyChangeSupport; import com.jgoodies.binding.tests.beans.EquityTestBean; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.value.Trigger; /** * A test case for class {@link IndirectPropertyChangeSupport} * that checks whether listeners are added, removed and re-registered properly. * Also tests the classes BeanAdapter and PresentationModel that use * the IndirectPropertyChangeSupport directly or indirectly. * * @author Karsten Lentzsch * @version $Revision: 1.16 $ */ public final class IndirectPropertyChangeSupportTest extends TestCase { // Re-Registering Listeners on Different Bean Types *********************** public void testReregisterListenerOnDifferentBeans() { testReregisterListener( new EquityTestBean("key1"), new EquityTestBean("key2"), new IndirectPropertyChangeSupport()); TestBean bean1 = new EquityTestBean("key1"); testReregisterListener( bean1, new EquityTestBean("key2"), new IndirectPropertyChangeSupport(bean1)); } public void testReregisterListenerOnEqualBeans() { testReregisterListener( new EquityTestBean("key1"), new EquityTestBean("key1"), new IndirectPropertyChangeSupport()); TestBean bean1 = new EquityTestBean("key1"); testReregisterListener( bean1, new EquityTestBean("key1"), new IndirectPropertyChangeSupport(bean1)); } public void testBeanAdapterReregisterListenerOnEqualBeans() { TestBean bean1 = new EquityTestBean("key1"); testReregisterListener( bean1, new EquityTestBean("key1"), new BeanAdapter(bean1)); bean1 = new EquityTestBean("key1"); testReregisterListener( bean1, new EquityTestBean("key1"), new BeanAdapter(bean1, false)); } public void testPresentationModelReregisterListenerOnEqualBeans() { TestBean bean1 = new EquityTestBean("key1"); testReregisterListener( bean1, new EquityTestBean("key1"), new PresentationModel(bean1)); bean1 = new EquityTestBean("key1"); testReregisterListener( bean1, new EquityTestBean("key1"), new PresentationModel(bean1, new Trigger())); } // Reusable Basic Test **************************************************** private void testReregisterListener( TestBean bean1, TestBean bean2, Object beanHolder) { String propertyName = "readWriteObjectProperty"; setBean(beanHolder, bean1); PropertyChangeReport report1a = new PropertyChangeReport(); PropertyChangeReport report1b = new PropertyChangeReport(); PropertyChangeReport report2a = new PropertyChangeReport(); PropertyChangeReport report2b = new PropertyChangeReport(); addIndirectListener(beanHolder, report1a); addIndirectListener(beanHolder, report1b); addIndirectListener(beanHolder, propertyName, report2a); addIndirectListener(beanHolder, propertyName, report2b); bean1.setReadWriteObjectProperty("bean1value1"); assertEquals("Changing the observed bean1 fires a change.", 1, report1a.eventCount()); assertEquals("All unnamed listeners receive the same number of events.", report1a.eventCount(), report1b.eventCount()); assertEquals("Changing the observed bean1 fires a named change.", 1, report2a.eventCount()); assertEquals("All named listeners receive the same number of events.", report2a.eventCount(), report2b.eventCount()); bean2.setReadWriteObjectProperty("bean2value1"); assertEquals("Changing the unobserved bean2 fires no change.", 1, report1a.eventCount()); assertEquals("Changing the unobserved bean2 fires no named change.", 1, report1a.eventCount()); // Change the bean setBean(beanHolder, bean2); bean1.setReadWriteObjectProperty("bean1value2"); assertEquals("Changing the unobserved bean1 fires no change.", 1, report1a.eventCount()); assertEquals("Changing the unobserved bean1 fires no named change.", 1, report2a.eventCount()); bean2.setReadWriteObjectProperty("bean2value2"); assertEquals("Changing the observed bean2 fires a change.", 2, report1a.eventCount()); assertEquals("2) All unnamed listeners receive the same number of events.", report1a.eventCount(), report1b.eventCount()); assertEquals("Changing the observed bean2 fires a change.", 2, report2a.eventCount()); assertEquals("2) All named listeners receive the same number of events.", report2a.eventCount(), report2b.eventCount()); removeIndirectListener(beanHolder, report1a); removeIndirectListener(beanHolder, report1b); removeIndirectListener(beanHolder, propertyName, report2a); removeIndirectListener(beanHolder, propertyName, report2b); // Change the bean back to bean1 setBean(beanHolder, bean1); bean1.setReadWriteObjectProperty("bean1value3"); assertEquals("Unregistered listeners receive no more events.", 2, report1a.eventCount()); assertEquals("All unregistered listeners receive no more events.", report1a.eventCount(), report1b.eventCount()); assertEquals("Deregistered named listeners receive no more events.", 2, report2a.eventCount()); assertEquals("All deregistered named listeners receive no more events.", report2a.eventCount(), report2b.eventCount()); } // Helper Code *********************************************************** private void setBean(Object beanHolder, TestBean newBean) { if (beanHolder instanceof IndirectPropertyChangeSupport) ((IndirectPropertyChangeSupport) beanHolder).setBean(newBean); else if (beanHolder instanceof BeanAdapter) ((BeanAdapter) beanHolder).setBean(newBean); else if (beanHolder instanceof PresentationModel) ((PresentationModel) beanHolder).setBean(newBean); else throw new IllegalArgumentException("Unknown bean holder type. " + "Must be one of: IndirectPropertyChangeSupport, BeanAdapter, PresentationModel"); } private void addIndirectListener(Object beanHolder, PropertyChangeListener listener) { if (beanHolder instanceof IndirectPropertyChangeSupport) ((IndirectPropertyChangeSupport) beanHolder).addPropertyChangeListener(listener); else if (beanHolder instanceof BeanAdapter) ((BeanAdapter) beanHolder).addBeanPropertyChangeListener(listener); else if (beanHolder instanceof PresentationModel) ((PresentationModel) beanHolder).addBeanPropertyChangeListener(listener); else throw new IllegalArgumentException("Unknown bean holder type. " + "Must be one of: IndirectPropertyChangeSupport, BeanAdapter, PresentationModel"); } private void addIndirectListener(Object beanHolder, String propertyName, PropertyChangeListener listener) { if (beanHolder instanceof IndirectPropertyChangeSupport) ((IndirectPropertyChangeSupport) beanHolder).addPropertyChangeListener(propertyName, listener); else if (beanHolder instanceof BeanAdapter) ((BeanAdapter) beanHolder).addBeanPropertyChangeListener(propertyName, listener); else if (beanHolder instanceof PresentationModel) ((PresentationModel) beanHolder).addBeanPropertyChangeListener(propertyName, listener); else throw new IllegalArgumentException("Unknown bean holder type. " + "Must be one of: IndirectPropertyChangeSupport, BeanAdapter, PresentationModel"); } private void removeIndirectListener(Object beanHolder, PropertyChangeListener listener) { if (beanHolder instanceof IndirectPropertyChangeSupport) ((IndirectPropertyChangeSupport) beanHolder).removePropertyChangeListener(listener); else if (beanHolder instanceof BeanAdapter) ((BeanAdapter) beanHolder).removeBeanPropertyChangeListener(listener); else if (beanHolder instanceof PresentationModel) ((PresentationModel) beanHolder).removeBeanPropertyChangeListener(listener); else throw new IllegalArgumentException("Unknown bean holder type. " + "Must be one of: IndirectPropertyChangeSupport, BeanAdapter, PresentationModel"); } private void removeIndirectListener(Object beanHolder, String propertyName, PropertyChangeListener listener) { if (beanHolder instanceof IndirectPropertyChangeSupport) ((IndirectPropertyChangeSupport) beanHolder).removePropertyChangeListener(propertyName, listener); else if (beanHolder instanceof BeanAdapter) ((BeanAdapter) beanHolder).removeBeanPropertyChangeListener(propertyName, listener); else if (beanHolder instanceof PresentationModel) ((PresentationModel) beanHolder).removeBeanPropertyChangeListener(propertyName, listener); else throw new IllegalArgumentException("Unknown bean holder type. " + "Must be one of: IndirectPropertyChangeSupport, BeanAdapter, PresentationModel"); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/ReflectionTest.java0000644000175000017500000002001411374522114027360 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.NumberFormat; import junit.framework.TestCase; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.tests.beans.TestBean; import com.jgoodies.binding.value.ConverterFactory; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * Checks reflection access to a bunch of ValueModel implementations. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ */ public final class ReflectionTest extends TestCase { /** * Checks reflection access to ValueModels returned by the BeanAdapter. */ public void testAccessToBeanAdapterModels() { BeanAdapter adapter = new BeanAdapter(new TestBean()); ValueModel valueModel = adapter.getValueModel("readWriteObjectProperty"); checkAccess(valueModel, null, "BeanAdapter's ValueModel"); } // Checking Access to ConverterFactory Converters ************************* /** * Checks reflection access to the converting ValueModel * returned by ConverterFactory.createBooleanNegator. */ public void testAccessToBooleanNegator() { ValueModel booleanNegator = ConverterFactory.createBooleanNegator( new ValueHolder(true)); checkAccess(booleanNegator, null, "ConverterFactory#createBooleanNegator"); } /** * Checks reflection access to the converting ValueModel * returned by ConverterFactory.createBooleanToStringConverter. */ public void testAccessToBooleanToStringConverter() { ValueModel stringConverter = ConverterFactory.createBooleanToStringConverter( new ValueHolder(true), "true", "false"); checkAccess(stringConverter, "true", "ConverterFactory#createBooleanToStringConverter"); } /** * Checks reflection access to the converting ValueModel * returned by ConverterFactory.createDoubleToIntegerConverter. */ public void testAccessToDoubleToIntegerConverter() { ValueModel doubleConverter = ConverterFactory.createDoubleToIntegerConverter( new ValueHolder(1d)); checkAccess(doubleConverter, new Integer(2), "ConverterFactory#createDoubleToIntegerConverter"); } /** * Checks reflection access to the converting ValueModel * returned by ConverterFactory.createFloatToIntegerConverter. */ public void testAccessToFloatToIntegerConverter() { ValueModel floatConverter = ConverterFactory.createFloatToIntegerConverter( new ValueHolder(1f)); checkAccess(floatConverter, new Integer(2), "ConverterFactory#createFloatToIntegerConverter"); } /** * Checks reflection access to the converting ValueModel * returned by ConverterFactory.createLongToIntegerConverter. */ public void testAccessToLongToIntegerConverter() { ValueModel longConverter = ConverterFactory.createLongToIntegerConverter( new ValueHolder(new Long(42))); checkAccess(longConverter, new Integer(2), "ConverterFactory#createLongToIntegerConverter"); } /** * Checks reflection access to the converting ValueModel * returned by ConverterFactory.createStringConverter. */ public void testAccessToStringConverter() { ValueModel stringConverter = ConverterFactory.createStringConverter( new ValueHolder(1967), NumberFormat.getIntegerInstance()); checkAccess(stringConverter, "512", "ConverterFactory#createStringConverter"); } // Helper Code ************************************************************ /** * Checks read-write-access to the given ValueModel using reflection. * * @param valueModel the ValueModel that implements the setter * @param newValue the test value for the setter * @param description used in failure message to describe the model */ private static void checkAccess( ValueModel valueModel, Object newValue, String description) { checkReadAccess (valueModel, description + "#getValue()"); checkWriteAccess(valueModel, newValue, description + "#setValue(Object)"); } /** * Checks read-access to the given ValueModel using reflection. * * @param valueModel the ValueModel that implements the getter * @param description used in failure message to describe the model */ private static void checkReadAccess(ValueModel valueModel, String description) { try { Method getter = valueModel.getClass().getMethod("getValue", new Class[]{}); getter.invoke(valueModel, new Object[]{}); } catch (NoSuchMethodException e) { fail("Inaccessible " + description); } catch (IllegalArgumentException e) { fail("IllegalArgumentException in " + description); } catch (IllegalAccessException e) { fail("IllegalAccessException in " + description); } catch (InvocationTargetException e) { fail("InvocationTargetException in " + description); } } /** * Checks write-access to the given ValueModel using reflection. * * @param valueModel the ValueModel that implements the setter * @param newValue the value to be set * @param description used in failure message to describe the model */ private static void checkWriteAccess( ValueModel valueModel, Object newValue, String description) { try { Method setter = valueModel.getClass().getMethod("setValue", new Class[]{Object.class}); setter.invoke(valueModel, new Object[]{newValue}); } catch (NoSuchMethodException e) { fail("Inaccessible " + description); } catch (IllegalArgumentException e) { fail("IllegalArgumentException in " + description); } catch (IllegalAccessException e) { fail("IllegalAccessException in " + description); } catch (InvocationTargetException e) { fail("InvocationTargetException in " + description); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/PropertyAdapterTest.java0000644000175000017500000010555111374522114030425 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.beans.PropertyChangeEvent; import java.beans.PropertyVetoException; import junit.framework.TestCase; import com.jgoodies.binding.beans.*; import com.jgoodies.binding.tests.beans.*; import com.jgoodies.binding.tests.event.PropertyChangeReport; import com.jgoodies.binding.tests.value.ValueHolderWithNewValueNull; import com.jgoodies.binding.tests.value.ValueHolderWithOldAndNewValueNull; import com.jgoodies.binding.tests.value.ValueHolderWithOldValueNull; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link PropertyAdapter}. * * @author Karsten Lentzsch * @version $Revision: 1.29 $ */ public final class PropertyAdapterTest extends TestCase { private TestBean model1; private TestBean model2; /** * @throws Exception in case of an unexpected problem */ @Override protected void setUp() throws Exception { super.setUp(); model1 = new TestBean(); model2 = new TestBean(); } /** * @throws Exception in case of an unexpected problem */ @Override protected void tearDown() throws Exception { super.tearDown(); model1 = null; model2 = null; } // Constructor Tests ****************************************************** /** * Verifies that we can adapt observable and non-observable objects * if we do not observe changes. */ public void testConstructorsAcceptAllBeansWhenNotObserving() { for (Object bean : BeanClasses.getBeans()) { try { new PropertyAdapter(bean, "property", false); new PropertyAdapter(new ValueHolder(bean, true), "property", false); } catch (PropertyUnboundException ex) { fail("Constructor must not try to observe with observeChanges == false"); } } } /** * Verifies that we can adapt and observe observables. */ public void testConstructorsAcceptToObserveObservables() { for (Object bean : BeanClasses.getObservableBeans()) { try { new PropertyAdapter(bean, "property", true); new PropertyAdapter(new ValueHolder(bean, true), "property", true); } catch (PropertyUnboundException ex) { fail("Constructor failed to accept to observe an observable." + "\nbean class=" + bean.getClass() + "\nexception=" + ex); } } } public void testConstructorRejectsNonIdentityCheckingBeanChannel() { try { new PropertyAdapter(new ValueHolder(null), "aProperty"); fail("Constructor must reject bean channel that has the identity check feature disabled."); } catch (IllegalArgumentException e) { // The expected behavior } } /** * Verifies that we cannot observe non-observables. */ public void testConstructorsRejectToObserveObservables() { for (Object bean : BeanClasses.getUnobservableBeans()) { try { new PropertyAdapter(bean, "property", true); fail("Constructor must reject to observe non-observables."); } catch (PropertyUnboundException ex) { // The expected behavior } try { new PropertyAdapter(new ValueHolder(bean, true), "property", true); fail("Constructor must reject to observe non-observables."); } catch (PropertyUnboundException ex) { // The expected behavior } } } /** * Verifies that we cannot observe non-observables. */ public void testConstructorsAcceptsToObserveObjectsThatSupportBoundProperties() { for (Object bean : BeanClasses.getBeans()) { try { boolean supportsBoundProperties = BeanUtils.supportsBoundProperties(bean.getClass()); new PropertyAdapter(bean, "property", supportsBoundProperties); new PropertyAdapter(new ValueHolder(bean, true), "property", supportsBoundProperties); } catch (PropertyUnboundException ex) { fail("Constructor failed to accept to observe an observable." + "\nbean class=" + bean.getClass() + "\nexception=" + ex); } } } public void testConstructorAcceptsNullBean() { try { new PropertyAdapter(null, "property", true); } catch (NullPointerException ex) { ex.printStackTrace(); fail("Constructor failed to accept a null bean." + "\nexception=" + ex); } } public void testConstructorRejectsNullPropertyName() { try { new PropertyAdapter(new TestBean(), null, true); fail("Constructor must reject a null property name."); } catch (NullPointerException ex) { // The expected behavior } } public void testConstructorRejectsEmptyPropertyName() { try { new PropertyAdapter(new TestBean(), "", true); fail("Constructor must reject an empty property name."); } catch (IllegalArgumentException ex) { // The expected behavior } } public void testAcceptsMultipleReleases() { PropertyAdapter adapter = new PropertyAdapter(model1, "readOnlyObjectProperty"); adapter.release(); adapter.release(); } // Testing Property Access ************************************************ /** */ public void testReadWriteProperties() { TestBean bean = new TestBean(); bean.setReadWriteObjectProperty("initialValue"); bean.setReadWriteBooleanProperty(false); bean.setReadWriteIntProperty(42); ValueModel objectAdapter = new PropertyAdapter(bean, "readWriteObjectProperty"); ValueModel booleanAdapter = new PropertyAdapter(bean, "readWriteBooleanProperty"); ValueModel intAdapter = new PropertyAdapter(bean, "readWriteIntProperty"); ValueModel integerAdapter = new PropertyAdapter(bean, "readWriteIntegerProperty"); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.getReadWriteObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.isReadWriteBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.getReadWriteIntProperty())); assertEquals(integerAdapter.getValue(), bean.getReadWriteIntegerProperty()); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(43); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); integerAdapter.setValue(integerValue); assertEquals(objectValue, bean.getReadWriteObjectProperty()); assertEquals(booleanValue, Boolean.valueOf(bean.isReadWriteBooleanProperty())); assertEquals(integerValue, new Integer(bean.getReadWriteIntProperty())); assertEquals(integerValue, bean.getReadWriteIntegerProperty()); } public void testReadWriteCustomProperties() { CustomAccessBean bean = new CustomAccessBean(); bean.writeRWObjectProperty("initialValue"); bean.writeRWBooleanProperty(false); bean.writeRWIntProperty(42); ValueModel objectAdapter = new PropertyAdapter(bean, "readWriteObjectProperty", "readRWObjectProperty", "writeRWObjectProperty"); ValueModel booleanAdapter = new PropertyAdapter(bean, "readWriteBooleanProperty", "readRWBooleanProperty", "writeRWBooleanProperty"); ValueModel intAdapter = new PropertyAdapter(bean, "readWriteIntProperty", "readRWIntProperty", "writeRWIntProperty"); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.readRWObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.readRWBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.readRWIntProperty())); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(43); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); assertEquals(objectValue, bean.readRWObjectProperty()); assertEquals(booleanValue, Boolean.valueOf(bean.readRWBooleanProperty())); assertEquals(integerValue, new Integer(bean.readRWIntProperty())); } public void testReadOnlyProperties() { TestBean bean = new TestBean(); bean.readOnlyObjectProperty = "testString"; bean.readOnlyBooleanProperty = true; bean.readOnlyIntProperty = 42; ValueModel objectAdapter = new PropertyAdapter(bean, "readOnlyObjectProperty"); ValueModel booleanAdapter = new PropertyAdapter(bean, "readOnlyBooleanProperty"); ValueModel intAdapter = new PropertyAdapter(bean, "readOnlyIntProperty"); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.getReadOnlyObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.isReadOnlyBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.getReadOnlyIntProperty())); try { objectAdapter.setValue("some"); fail("Adapter must reject writing of read-only properties."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } public void testReadOnlyCustomProperties() { CustomAccessBean bean = new CustomAccessBean(); bean.readOnlyObjectProperty = "testString"; bean.readOnlyBooleanProperty = true; bean.readOnlyIntProperty = 42; ValueModel objectAdapter = new PropertyAdapter(bean, "readOnlyObjectProperty", "readROObjectProperty", null); ValueModel booleanAdapter = new PropertyAdapter(bean, "readOnlyBooleanProperty", "readROBooleanProperty", null); ValueModel intAdapter = new PropertyAdapter(bean, "readOnlyIntProperty", "readROIntProperty", null); // Adapter values equal the bean property values. assertEquals(objectAdapter.getValue(), bean.readROObjectProperty()); assertEquals(booleanAdapter.getValue(), Boolean.valueOf(bean.readROBooleanProperty())); assertEquals(intAdapter.getValue(), new Integer(bean.readROIntProperty())); try { objectAdapter.setValue("some"); fail("Adapter must reject writing of read-only properties."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } /** */ public void testWriteOnlyProperties() { TestBean bean = new TestBean(); ValueModel objectAdapter = new PropertyAdapter(bean, "writeOnlyObjectProperty"); ValueModel booleanAdapter = new PropertyAdapter(bean, "writeOnlyBooleanProperty"); ValueModel intAdapter = new PropertyAdapter(bean, "writeOnlyIntProperty"); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(42); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); assertEquals(objectValue, bean.writeOnlyObjectProperty); assertEquals(booleanValue, Boolean.valueOf(bean.writeOnlyBooleanProperty)); assertEquals(integerValue, new Integer(bean.writeOnlyIntProperty)); try { objectAdapter.getValue(); fail("Adapter must reject to read a write-only property."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } public void testWriteOnlyCustomProperties() { CustomAccessBean bean = new CustomAccessBean(); ValueModel objectAdapter = new PropertyAdapter(bean, "writeOnlyObjectProperty", null, "writeWOObjectProperty"); ValueModel booleanAdapter = new PropertyAdapter(bean, "writeOnlyBooleanProperty", null, "writeWOBooleanProperty"); ValueModel intAdapter = new PropertyAdapter(bean, "writeOnlyIntProperty", null, "writeWOIntProperty"); Object objectValue = "testString"; Boolean booleanValue = Boolean.TRUE; Integer integerValue = new Integer(42); objectAdapter.setValue(objectValue); booleanAdapter.setValue(booleanValue); intAdapter.setValue(integerValue); assertEquals(objectValue, bean.writeOnlyObjectProperty); assertEquals(booleanValue, Boolean.valueOf(bean.writeOnlyBooleanProperty)); assertEquals(integerValue, new Integer(bean.writeOnlyIntProperty)); try { objectAdapter.getValue(); fail("Adapter must reject to read a write-only property."); } catch (UnsupportedOperationException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } /** * Checks the write access to a constrained property without veto. */ public void testWriteConstrainedPropertyWithoutVeto() { TestBean bean = new TestBean(); PropertyAdapter adapter = new PropertyAdapter(bean, "constrainedProperty"); try { bean.setConstrainedProperty("value1"); } catch (PropertyVetoException e1) { fail("Unexpected veto for value1."); } assertEquals("Bean has the initial value1.", bean.getConstrainedProperty(), "value1"); adapter.setValue("value2a"); assertEquals("Bean now has the value2a.", bean.getConstrainedProperty(), "value2a"); try { adapter.setVetoableValue("value2b"); } catch (PropertyVetoException e) { fail("Unexpected veto for value2b."); } assertEquals("Bean now has the value2b.", bean.getConstrainedProperty(), "value2b"); } /** * Checks the write access to a constrained property with veto. */ public void testWriteConstrainedPropertyWithVeto() { TestBean bean = new TestBean(); PropertyAdapter adapter = new PropertyAdapter(bean, "constrainedProperty"); try { bean.setConstrainedProperty("value1"); } catch (PropertyVetoException e1) { fail("Unexpected veto for value1."); } assertEquals("Bean has the initial value1.", bean.getConstrainedProperty(), "value1"); // Writing with a veto bean.addVetoableChangeListener(new VetoableChangeRejector()); adapter.setValue("value2a"); assertEquals("Bean still has the value1.", bean.getConstrainedProperty(), "value1"); try { adapter.setVetoableValue("value2b"); fail("Couldn't set the valid value1"); } catch (PropertyVetoException e) { PropertyChangeEvent pce = e.getPropertyChangeEvent(); assertEquals("The veto's old value is value1.", pce.getOldValue(), "value1"); assertEquals("The veto's new value is value2b.", pce.getNewValue(), "value2b"); } assertEquals("After setting value2b, the bean still has the value1.", bean.getConstrainedProperty(), "value1"); } /** * Verifies that the reader and writer can be located in different * levels of a class hierarchy. */ public void testReaderWriterSplittedInHierarchy() { ReadWriteHierarchyBean bean = new ReadWriteHierarchyBean(); ValueModel adapter = new PropertyAdapter(bean, "property"); Object value1 = "Ewa"; String value2 = "Karsten"; adapter.setValue(value1); assertEquals(value1, bean.getProperty()); bean.setProperty(value2); assertEquals(value2, adapter.getValue()); } /** * Tests access to properties that are described by a BeanInfo class. */ public void testBeanInfoProperties() { CustomBean bean = new CustomBean(); ValueModel adapter = new PropertyAdapter(bean, "readWriteObjectProperty"); Object value1 = "Ewa"; String value2 = "Karsten"; adapter.setValue(value1); assertEquals(value1, bean.getReadWriteObjectProperty()); bean.setReadWriteObjectProperty(value2); assertEquals(value2, adapter.getValue()); try { new PropertyAdapter(bean, "readWriteIntProperty"); fail("Adapter must not find properties that " + "have been excluded by a custom BeanInfo."); } catch (PropertyNotFoundException e) { // The expected behavior } } public void testAbsentProperties() { TestBean bean = new TestBean(); try { new PropertyAdapter(bean, "absentObjectProperty"); fail("Adapter must reject an absent object property."); } catch (PropertyNotFoundException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new PropertyAdapter(bean, "absentBooleanProperty"); fail("Adapter must reject an absent boolean property."); } catch (PropertyNotFoundException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new PropertyAdapter(bean, "absentIntProperty"); fail("Adapter must reject an absent int property."); } catch (PropertyNotFoundException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } public void testIllegalPropertyAccess() { TestBean bean = new TestBean(); try { new PropertyAdapter(bean, "noAccess").getValue(); fail("Adapter must report read-access problems."); } catch (PropertyAccessException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new PropertyAdapter(bean, "noAccess").setValue("Fails"); fail("Adapter must report write-access problems."); } catch (PropertyAccessException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } try { new PropertyAdapter(bean, "readWriteIntProperty").setValue(1967L); fail("Adapter must report IllegalArgumentExceptions."); } catch (PropertyAccessException ex) { // The expected behavior } catch (Exception e) { fail("Unexpected exception=" + e); } } // Testing Update Notifications ******************************************* public void testSetPropertySendsUpdates() { AbstractValueModel adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("value", changeReport); model1.setReadWriteObjectProperty("Karsten"); assertEquals("First property change.", 1, changeReport.eventCount()); model1.setReadWriteObjectProperty("Ewa"); assertEquals("Second property change.", 2, changeReport.eventCount()); model1.setReadWriteObjectProperty(model1.getReadWriteObjectProperty()); assertEquals("Property change repeated.", 2, changeReport.eventCount()); } public void testSetValueSendsUpdates() { AbstractValueModel adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("value", changeReport); adapter.setValue("Johannes"); assertEquals("Value change.", 1, changeReport.eventCount()); adapter.setValue(adapter.getValue()); assertEquals("Value set again.", 1, changeReport.eventCount()); } public void testListensOnlyToAdaptedProperty() { AbstractValueModel adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("value", changeReport); model1.setReadWriteObjectProperty("Karsten"); assertEquals("Observed change.", 1, changeReport.eventCount()); model1.setReadWriteIntProperty(3); assertEquals("Ignored change.", 1, changeReport.eventCount()); } public void testBeanChangeSendsUpdates() { model1.setReadWriteObjectProperty("initialValue"); PropertyAdapter adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("value", changeReport); adapter.setBean(model1); assertEquals("First bean set.", 0, changeReport.eventCount()); adapter.setBean(new TestBean()); assertEquals("Second bean set.", 1, changeReport.eventCount()); } public void testBeanChangeIgnoresOldBeanNull() { testBeanChangeIgnoresMissingOldOrNullValues(new ValueHolderWithOldValueNull(null)); } public void testBeanChangeIgnoresNewBeanNull() { testBeanChangeIgnoresMissingOldOrNullValues(new ValueHolderWithNewValueNull(null)); } public void testBeanChangeIgnoresOldAndNewBeanNull() { testBeanChangeIgnoresMissingOldOrNullValues(new ValueHolderWithOldAndNewValueNull(null)); } private void testBeanChangeIgnoresMissingOldOrNullValues(ValueModel beanChannel) { TestBean bean1 = new TestBean(); TestBean bean2 = new TestBean(); beanChannel.setValue(bean1); ValueModel adapter = new PropertyAdapter(beanChannel, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addValueChangeListener(changeReport); beanChannel.setValue(bean2); bean1.setReadWriteObjectProperty("bean1value"); assertEquals("No event if modifying the old bean", 0, changeReport.eventCount()); bean2.setReadWriteObjectProperty("bean2value"); assertEquals("Fires event if modifying the new bean", 1, changeReport.eventCount()); } public void testMulticastAndNamedPropertyChangeEvents() { AbstractValueModel adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("value", changeReport); model1.setReadWriteObjectProperty("Karsten"); assertEquals("Adapted property changed.", 1, changeReport.eventCount()); model1.setReadWriteBooleanProperty(false); assertEquals("Another property changed.", 1, changeReport.eventCount()); model1.setReadWriteObjectProperties(null, false, 3); assertEquals("Multiple properties changed.", 2, changeReport.eventCount()); } public void testMulticastFiresProperNewValue() { AbstractValueModel adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("value", changeReport); Object theNewObjectValue = "The new value"; model1.setReadWriteObjectProperties(theNewObjectValue, false, 3); Object eventsNewValue = changeReport.lastNewValue(); assertEquals("Multicast fires proper new value .", theNewObjectValue, eventsNewValue); } // Misc ******************************************************************* /** * Checks that #setBean changes the bean and moves the * PropertyChangeListeners to the new bean. */ public void testSetBean() { Object value1_1 = "value1.1"; Object value1_2 = "value1.2"; Object value2_1 = "value2.1"; Object value2_2 = "value2.2"; Object value2_3 = "value2.3"; model1.setReadWriteObjectProperty(value1_1); model2.setReadWriteObjectProperty(value2_1); PropertyAdapter adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); adapter.setBean(model2); assertSame( "Bean has not been changed.", adapter.getBean(), model2); assertSame( "Bean change does not answer the new beans's value.", adapter.getValue(), value2_1); adapter.setValue(value2_2); assertSame( "Bean change does not set the new bean's property.", model2.getReadWriteObjectProperty(), value2_2); model1.setReadWriteObjectProperty(value1_2); assertSame("Adapter listens to old bean after bean change.", adapter.getValue(), value2_2); model2.setReadWriteObjectProperty(value2_3); assertSame("Adapter does not listen to new bean after bean change.", adapter.getValue(), value2_3); } /** * Tests that we can change the bean if we adapt a write-only property. * Changing the bean normally calls the property's getter to request * the old value that is used in the fired PropertyChangeEvent. */ public void testSetBeanOnWriteOnlyProperty() { PropertyAdapter adapter = new PropertyAdapter(model1, "writeOnlyObjectProperty", true); adapter.setBean(model2); } /** * Tests that we can change the read/write state of the bean. */ public void testSetBeanChangesReadWriteState() { ReadWriteBean readWriteBean = new ReadWriteBean(); ReadOnlyBean readOnlyBean = new ReadOnlyBean(); WriteOnlyBean writeOnlyBean = new WriteOnlyBean(); // From/to readWriteBean to all other read/write states PropertyAdapter adapter = new PropertyAdapter(readWriteBean, "property"); adapter.setBean(null); adapter.setBean(readWriteBean); adapter.setBean(readOnlyBean); adapter.setBean(readWriteBean); adapter.setBean(writeOnlyBean); adapter.setBean(readWriteBean); // From/to writeOnlyBean to all other states adapter.setBean(writeOnlyBean); adapter.setBean(null); adapter.setBean(writeOnlyBean); adapter.setBean(readOnlyBean); adapter.setBean(writeOnlyBean); // From/to readOnlyBean to all other states adapter.setBean(readOnlyBean); adapter.setBean(null); adapter.setBean(readOnlyBean); } /** * Checks that bean changes are reported. */ public void testBeanIsBoundProperty() { PropertyAdapter adapter = new PropertyAdapter(model1, "readWriteObjectProperty"); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("bean", changeReport); adapter.setBean(model2); assertEquals("Bean changed.", 1, changeReport.eventCount()); adapter.setBean(model2); assertEquals("Bean unchanged.", 1, changeReport.eventCount()); adapter.setBean(null); assertEquals("Bean set to null.", 2, changeReport.eventCount()); adapter.setBean(model1); assertEquals("Bean changed from null.", 3, changeReport.eventCount()); } public void testBeanChangeFiresThreeBeanEvents() { model1.setReadWriteObjectProperty("initialValue"); PropertyAdapter adapter = new PropertyAdapter((TestBean) null, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener(changeReport); adapter.setBean(model1); assertEquals("Changing the bean fires three events: before, changing, after." + "It also fires a change event.", 4, changeReport.eventCount()); } public void testEqualBeanChangeFiresThreeBeanEvents() { Object bean1 = new EquityTestBean("bean"); Object bean2 = new EquityTestBean("bean"); assertEquals("The two test beans are equal.", bean1, bean2); assertNotSame("The two test beans are not the same.", bean1, bean2); PropertyAdapter adapter1 = new PropertyAdapter(bean1, "readWriteObjectProperty", true); PropertyChangeReport changeReport1 = new PropertyChangeReport(); adapter1.addPropertyChangeListener(changeReport1); adapter1.setBean(bean2); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport1.eventCount()); PropertyAdapter adapter2 = new PropertyAdapter(null, "readWriteObjectProperty", true); adapter2.setBean(bean1); PropertyChangeReport changeReport2 = new PropertyChangeReport(); adapter2.addPropertyChangeListener(changeReport2); adapter2.setBean(bean2); assertEquals("Changing the bean fires three events: before, changing, after.", 3, changeReport2.eventCount()); } public void testChangedState() { PropertyAdapter adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); assertEquals("The initial changed state is false.", false, adapter.isChanged()); model1.setReadWriteObjectProperty("aBrandNewValue"); assertEquals("Changing the bean turns the changed state to true.", true, adapter.isChanged()); adapter.resetChanged(); assertEquals("Resetting changes turns the changed state to false.", false, adapter.isChanged()); model1.setReadWriteObjectProperty("anotherValue"); assertEquals("Changing the bean turns the changed state to true again.", true, adapter.isChanged()); adapter.setBean(model2); assertEquals("Changing the bean resets the changed state.", false, adapter.isChanged()); } public void testChangedStateFiresEvents() { PropertyAdapter adapter = new PropertyAdapter(model1, "readWriteObjectProperty", true); PropertyChangeReport changeReport = new PropertyChangeReport(); adapter.addPropertyChangeListener("changed", changeReport); model1.setReadWriteObjectProperty("aBrandNewValue"); adapter.resetChanged(); model1.setReadWriteObjectProperty("anotherValue"); adapter.setBean(model2); assertEquals("The changed state changed four times.", 4, changeReport.eventCount()); } // Misc ******************************************************************* /** * Checks that the cached PropertyDescriptor is available when needed. */ public void testPropertyDescriptorCache() { ValueModel beanChannel = new ValueHolder(null, true); PropertyAdapter adapter = new PropertyAdapter(beanChannel, "readWriteObjectProperty", true); beanChannel.setValue(new TestBean()); adapter.setValue("Test"); } // Null Bean ************************************************************** public void testNullBean() { PropertyAdapter adapter = new PropertyAdapter((TestBean) null, "readWriteObjectProperty", true); adapter.toString(); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/SpinnerAdapterFactoryTest.java0000644000175000017500000001511511374522114031543 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests; import java.util.Date; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import junit.framework.TestCase; import com.jgoodies.binding.adapter.SpinnerAdapterFactory; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A test case for class {@link SpinnerAdapterFactory}. * * @author Karsten Lentzsch * @version $Revision: 1.6 $ */ public final class SpinnerAdapterFactoryTest extends TestCase { // #connect *************************************************************** public void testConnectRejectsNullParameters() { SpinnerModel spinnerModel = new SpinnerNumberModel(1, 0, 10, 2); ValueModel valueModel = new ValueHolder(1); Object defaultValue = new Integer(1); try { SpinnerAdapterFactory.connect(null, valueModel, defaultValue); fail("#connect must reject null spinner models."); } catch (NullPointerException e) { // The expected behavior } try { SpinnerAdapterFactory.connect(spinnerModel, null, defaultValue); fail("#connect must reject null value models."); } catch (NullPointerException e) { // The expected behavior } try { SpinnerAdapterFactory.connect(spinnerModel, valueModel, null); fail("#connect must reject null default values."); } catch (NullPointerException e) { // The expected behavior } } // #createDateAdapter ***************************************************** public void testCreateDateAdapterRejectsNullParameters() { try { SpinnerAdapterFactory.createDateAdapter(null, new Date()); fail("The factory must reject null value models."); } catch (NullPointerException e) { // The expected behavior } try { SpinnerAdapterFactory.createDateAdapter(new ValueHolder(), null); fail("The factory must reject null default dates."); } catch (NullPointerException e) { // The expected behavior } } public void testCreateDateAdapterAcceptsInitialNullValueModelValues() { Object initialValue = null; ValueModel valueModel = new ValueHolder(initialValue); Date defaultDate = new Date(); SpinnerModel spinnerModel = SpinnerAdapterFactory.createDateAdapter( valueModel, defaultDate); assertEquals("The spinner model's value is the default date.", defaultDate, spinnerModel.getValue()); } public void testCreateDateAdapterAcceptsNullValueModelValues() { Object initialValue = new Date(51267); ValueModel valueModel = new ValueHolder(initialValue); Date defaultDate = new Date(); SpinnerModel spinnerModel = SpinnerAdapterFactory.createDateAdapter( valueModel, defaultDate); assertEquals("The spinner model's value is the value model's value.", valueModel.getValue(), spinnerModel.getValue()); valueModel.setValue(null); assertEquals("The spinner model's value is the default date.", defaultDate, spinnerModel.getValue()); } // #createNumberAdapter *************************************************** public void testCreateNumberAdapterRejectsNullValueModel() { try { SpinnerAdapterFactory.createNumberAdapter(null, 0, 0, 10, 1); fail("The factory must reject null value models."); } catch (NullPointerException e) { // The expected behavior } } public void testCreateNumberAdapterAcceptsInitialNullValueModelValues() { Object initialValue = null; ValueModel valueModel = new ValueHolder(initialValue); int defaultValue = 5; SpinnerModel spinnerModel = SpinnerAdapterFactory.createNumberAdapter( valueModel, defaultValue, 0, 10, 2); assertEquals("The spinner model's value is the default value.", new Integer(defaultValue), spinnerModel.getValue()); } public void testCreateNumberAdapterAcceptsNullValueModelValues() { Object initialValue = new Integer(1); ValueModel valueModel = new ValueHolder(initialValue); int defaultValue = 5; SpinnerModel spinnerModel = SpinnerAdapterFactory.createNumberAdapter( valueModel, defaultValue, 0, 10, 2); assertEquals("The spinner model's value is the value model's value.", valueModel.getValue(), spinnerModel.getValue()); valueModel.setValue(null); assertEquals("The spinner model's value is the default value.", new Integer(defaultValue), spinnerModel.getValue()); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/0000755000175000017500000000000011374522114024707 5ustar twernertwernerjgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/ItemChangeReport.java0000644000175000017500000000574711374522114030767 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.event; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; /** * An ItemListener that stores the received ItemEvents. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ * @see ItemEvent */ public final class ItemChangeReport extends AbstractChangeReport implements ItemListener { /** * The observed event source has changed the item state. * Adds the event to the list of stored events. * * @param evt the change event to be handled */ public void itemStateChanged(ItemEvent evt) { addEvent(evt); if (isLogActive()) { System.out.println("Source:" + evt.getSource()); System.out.println("Item: " + evt.getItem()); System.out.println("State change: " + evt.getStateChange()); } } /** * Returns the state change of the last ItemEvent. * * @return the state change of the last ItemEvent. * @see ItemEvent#getStateChange() */ public int lastStateChange() { return getLastEvent().getStateChange(); } public boolean isLastStateChangeSelected() { return lastStateChange() == ItemEvent.SELECTED; } public boolean isLastStateChangeDeselected() { return lastStateChange() == ItemEvent.DESELECTED; } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/ListDataReport.java0000644000175000017500000001520511374522114030456 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.event; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; /** * A ListDataListener that stores the received ListDataEvents. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ */ public final class ListDataReport implements ListDataListener { /** * Holds a list of all received ListDataEvents. */ private final List allEvents = new LinkedList(); /** * Holds a list of all received add ListDataEvents. */ private final List addEvents = new LinkedList(); /** * Holds a list of all received remove ListDataEvents. */ private final List removeEvents = new LinkedList(); /** * Holds a list of all received change ListDataEvents. */ private final List changeEvents = new LinkedList(); // Implementing the ListDataListener Interface *************************** /** * Sent after the indices in the index0,index1 * interval have been inserted in the data model. * The new interval includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalAdded(ListDataEvent evt) { allEvents.add(0, evt); addEvents.add(0, evt); } /** * Sent after the indices in the index0,index1 interval * have been removed from the data model. The interval * includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalRemoved(ListDataEvent evt) { allEvents.add(0, evt); removeEvents.add(0, evt); } /** * Sent when the contents of the list has changed in a way * that's too complex to characterize with the previous * methods. For example, this is sent when an item has been * replaced. Index0 and index1 bracket the change. * * @param evt a ListDataEvent encapsulating the * event information */ public void contentsChanged(ListDataEvent evt) { allEvents.add(0, evt); changeEvents.add(0, evt); } // Public Report API ***************************************************** private ListDataEvent getEvent(int index) { return allEvents.get(index); } public ListDataEvent lastEvent() { return eventCount() > 0 ? getEvent(0) : null; } public ListDataEvent previousEvent() { return eventCount() > 1 ? getEvent(1) : null; } public List eventList() { return allEvents; } public int eventCount() { return allEvents.size(); } public int eventCountAdd() { return addEvents.size(); } public int eventCountRemove() { return removeEvents.size(); } public int eventCountChange() { return changeEvents.size(); } public boolean hasEvents() { return !allEvents.isEmpty(); } public void clearEventList() { allEvents.clear(); } // ************************************************************************ /** * Compares this report's event list with the event list * of the given object and ignores the source of the contained * ListDataEvents that may differ. * * @param o the object to compare with * @return true if equal, false if not * * @see java.util.AbstractList#equals(Object) */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof ListDataReport)) return false; ListIterator e1 = eventList().listIterator(); ListIterator e2 = ((ListDataReport) o).eventList().listIterator(); while (e1.hasNext() && e2.hasNext()) { ListDataEvent evt1 = e1.next(); ListDataEvent evt2 = e2.next(); if (!equalListDataEvents(evt1, evt2)) return false; } return !(e1.hasNext() || e2.hasNext()); } /** * Poor but valid implementation. Won't be used. * * @return this reports hash code */ @Override public int hashCode() { return eventList().size(); } /** * Checks and answers whether the two given ListDataEvents * have the same type and indices. The source may differ. */ private boolean equalListDataEvents(ListDataEvent evt1, ListDataEvent evt2) { return evt1.getType() == evt2.getType() && evt1.getIndex0() == evt2.getIndex0() && evt1.getIndex1() == evt2.getIndex1(); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/package.html0000644000175000017500000000464111374522114027175 0ustar twernertwerner Consists of test helper classes that track and count events.

Related Documentation

For more information see: @see com.jgoodies.binding.tests @see com.jgoodies.binding.tests.beans @see com.jgoodies.binding.tests.value jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/ChangeReport.java0000644000175000017500000000456711374522114030147 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.event; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * A PropertyChangeListener that stores the received PropertyChangeEvents. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ * @see ChangeEvent */ public final class ChangeReport extends AbstractChangeReport implements ChangeListener { /** * The observed event source has been changed. * Adds the event to the list of stored events. * * @param evt the change event to be handled */ public void stateChanged(ChangeEvent evt) { addEvent(evt); if (isLogActive()) { System.out.println("Source:" + evt.getSource()); } } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/ListSizeConstraintChecker.java0000644000175000017500000000550711374522114032661 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.event; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import junit.framework.Assert; /** * Compares the size reported by the ListDataEvents with the ListModel size. * * @author Karsten Lentzsch * @version $Revision: 1.7 $ */ public final class ListSizeConstraintChecker implements ListDataListener { private int size; public ListSizeConstraintChecker(int initialSize) { this.size = initialSize; } public void contentsChanged(ListDataEvent e) { assertSize(e); } public void intervalAdded(ListDataEvent e) { int addedElements = e.getIndex1() - e.getIndex0() + 1; size += addedElements; assertSize(e); } public void intervalRemoved(ListDataEvent e) { int removedElements = e.getIndex1() - e.getIndex0() + 1; size -= removedElements; assertSize(e); } private void assertSize(ListDataEvent e) { Assert.assertEquals( "The ListModel size equals the size reported by ListDataEvents.", ((ListModel) e.getSource()).getSize(), size); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/ListSelectionReport.java0000644000175000017500000001122211374522114031525 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.event; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; /** * A ListDataListener that stores the received ListDataEvents. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ */ public final class ListSelectionReport implements ListSelectionListener { /** * Holds a list of all received ListSelectionEvent. */ private final List allEvents = new LinkedList(); // Implementing the ListSelectionListener Interface *************************** /** * Called whenever the value of the selection changes. * * @param evt the event that characterizes the change. */ public void valueChanged(ListSelectionEvent evt) { allEvents.add(0, evt); } // Public Report API ***************************************************** public ListSelectionEvent lastEvent() { return allEvents.isEmpty() ? null : allEvents.get(0); } public List eventList() { return allEvents; } public int eventCount() { return allEvents.size(); } public boolean hasEvents() { return !allEvents.isEmpty(); } // ************************************************************************ /** * Compares this report's event list with the event list * of the given object and ignores the source of the contained * ListDataEvents that may differ. * * @param o the object to compare with * @return true if equal, false if not * * @see java.util.AbstractList#equals(Object) */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof ListSelectionReport)) return false; ListIterator e1 = eventList().listIterator(); ListIterator e2 = ((ListSelectionReport) o).eventList().listIterator(); while (e1.hasNext() && e2.hasNext()) { ListSelectionEvent evt1 = e1.next(); ListSelectionEvent evt2 = e2.next(); if (!equalListDataEvents(evt1, evt2)) return false; } return !(e1.hasNext() || e2.hasNext()); } /** * Poor but valid implementation. Won't be used. * * @return this report's hash code */ @Override public int hashCode() { return eventList().size(); } /** * Checks and answers whether the two given ListSelectionEvents have * the same first and last index. The source and adjusting state may differ. */ private boolean equalListDataEvents(ListSelectionEvent evt1, ListSelectionEvent evt2) { return evt1.getFirstIndex() == evt2.getFirstIndex() && evt1.getLastIndex() == evt2.getLastIndex(); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/AbstractChangeReport.java0000644000175000017500000000530711374522114031624 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.event; import java.util.LinkedList; import java.util.List; /** * The abstract superclass of the different change report implementations. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ * * @param the type of the events tracked by the report */ abstract class AbstractChangeReport { /** * Holds a list of all received events. */ protected List events = new LinkedList(); /** * If true, property changes are logged to the System console. */ private boolean logActive; protected boolean isLogActive() { return logActive; } public void setLogActive(boolean active) { this.logActive = active; } protected void addEvent(E event) { events.add(0, event); } public int eventCount() { return events.size(); } public boolean hasEvents() { return !events.isEmpty(); } protected E getLastEvent() { return events.isEmpty() ? null : events.get(0); } } jgoodies-binding-2.1.0/src/test/com/jgoodies/binding/tests/event/PropertyChangeReport.java0000644000175000017500000000676511374522114031716 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.tests.event; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; /** * A PropertyChangeListener that stores the received PropertyChangeEvents. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ * @see PropertyChangeListener */ public final class PropertyChangeReport extends AbstractChangeReport implements PropertyChangeListener { /** * The observed property has been changed. * Adds the event to the list of stored events. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { addEvent(evt); if (isLogActive()) { boolean same = evt.getOldValue() == evt.getNewValue(); System.out.println(); System.out.println("Source:" + evt.getSource()); System.out.println("Property:" + evt.getPropertyName()); System.out.println("Old value:" + evt.getOldValue()); System.out.println("New value:" + evt.getNewValue()); System.out.println("Old==new: " + same); } } public int multicastEventCount() { int count = 0; for (PropertyChangeEvent event : events) { if (event.getPropertyName() == null) count++; } return 0; } public int namedEventCount() { return eventCount() - multicastEventCount(); } public Object lastOldValue() { return getLastEvent().getOldValue(); } public Object lastNewValue() { return getLastEvent().getNewValue(); } public boolean lastOldBooleanValue() { return ((Boolean) lastOldValue()).booleanValue(); } public boolean lastNewBooleanValue() { return ((Boolean) lastNewValue()).booleanValue(); } } jgoodies-binding-2.1.0/src/core/0000755000175000017500000000000011374522114016402 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/overview.html0000644000175000017500000000646511374522114021151 0ustar twernertwerner This document describes the API of the JGoodies Binding, a library that connects object properties with UI components. The Binding:
  • reduces the code necessary for object presentation,
  • stream-lines the development process for data binding,
  • provides advanced features for automatic update notification,
  • assists you in separating the domain and presentation layers.

Since version 2.0 the JGoodies Binding requires Java 5 or later. Since version 2.1 it requires the JGoodies Common library. Binding versions for Java 1.4 are available in the JGoodies Archive.

Getting Started

You can find presentation about Binding techniques at the JGoodies Articles page. Also check out the User's Guide in the accompanying HTML documentation. The accompanying tutorial sources demonstrate how to work with Binding. The package descriptions in these JavaDocs provide a reference and many JavaDoc class comments contain example code. Also Martin Fowler describes the Presentation Model pattern in the upcoming additions to his "Patterns of Enterprise Application Architecture". jgoodies-binding-2.1.0/src/core/com/0000755000175000017500000000000011374522114017160 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/0000755000175000017500000000000011374522114020763 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/binding/0000755000175000017500000000000011374522114022375 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/0000755000175000017500000000000011374522114023465 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/IndirectPropertyChangeSupport.java0000644000175000017500000004103111374522114032340 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A helper class for observing changes in bound bean properties * where the target bean changes. * * Provides two access styles to the target bean that holds the observed * property: you can specify a bean directly, * or you can use a bean channel to access the bean indirectly. * In the latter case you specify a ValueModel * that holds the bean that in turn holds the observed properties.

* * If the target bean is {@code null}, it won't report any changes.

* * It is recommended to remove all listener by invoking #removeAll * if the observed bean lives much longer than this change support instance. * As an alternative you may use event listener lists that are based * on WeakReferences.

* * Constraints: All target bean classes must support * bound properties, i. e. must provide the following pair of methods * for registration of multicast property change event listeners: *

 * public void addPropertyChangeListener(PropertyChangeListener x);
 * public void removePropertyChangeListener(PropertyChangeListener x);
 * 
* and the following methods for listening on named properties: *
 * public void addPropertyChangeListener(String, PropertyChangeListener x);
 * public void removePropertyChangeListener(String, PropertyChangeListener x);
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.12 $ * * @see PropertyChangeEvent * @see PropertyChangeListener * @see PropertyChangeSupport * @see com.jgoodies.binding.beans.BeanAdapter */ public final class IndirectPropertyChangeSupport { /** * Holds a ValueModel that holds the bean, that in turn * holds the adapted property. * * @see #getBean() * @see #setBean(Object) */ private final ValueModel beanChannel; /** * Holds the PropertyChangeListeners that are registered for all bound * properties of the target bean. If the target bean changes, * these listeners are removed from the old bean and added to the new bean. * * @see #addPropertyChangeListener(PropertyChangeListener) * @see #removePropertyChangeListener(PropertyChangeListener) * @see #getPropertyChangeListeners() */ private final List listenerList; /** * Maps property names to the list of PropertyChangeListeners that are * registered for the associated bound property of the target bean. * If the target bean changes, these listeners are removed from * the old bean and added to the new bean. * * @see #addPropertyChangeListener(String, PropertyChangeListener) * @see #removePropertyChangeListener(String, PropertyChangeListener) * @see #getPropertyChangeListeners(String) */ private final Map> namedListeners; // Instance creation **************************************************** /** * Constructs an IndirectPropertyChangeSupport that has no bean set. */ public IndirectPropertyChangeSupport() { this(new ValueHolder(null, true)); } /** * Constructs an IndirectPropertyChangeSupport with the given initial bean. * * @param bean the initial bean */ public IndirectPropertyChangeSupport(Object bean) { this(new ValueHolder(bean, true)); } /** * Constructs an IndirectPropertyChangeSupport using the given bean channel. * * @param beanChannel the ValueModel that holds the bean */ public IndirectPropertyChangeSupport(ValueModel beanChannel) { this.beanChannel = checkNotNull(beanChannel, "The bean channel must not be null."); listenerList = new ArrayList(); namedListeners = new HashMap>(); beanChannel.addValueChangeListener(new BeanChangeHandler()); } // Accessors ************************************************************ /** * Returns the Java Bean that holds the observed properties. * * @return the Bean that holds the observed properties * * @see #setBean(Object) */ public Object getBean() { return beanChannel.getValue(); } /** * Sets a new Java Bean as holder of the observed properties. * Removes all registered listeners from the old bean and * adds them to the new bean. * * @param newBean the new holder of the observed properties * * @see #getBean() */ public void setBean(Object newBean) { beanChannel.setValue(newBean); } // Managing Property Change Listeners ************************************* /** * Adds a PropertyChangeListener to the list of bean listeners. * The listener is registered for all bound properties of the target bean. *

* * If listener is {@code null}, no exception is thrown and no action is performed. * * @param listener the PropertyChangeListener to be added * * @see #removePropertyChangeListener(PropertyChangeListener) * @see #removePropertyChangeListener(String, PropertyChangeListener) * @see #addPropertyChangeListener(String, PropertyChangeListener) * @see #getPropertyChangeListeners() */ public synchronized void addPropertyChangeListener( PropertyChangeListener listener) { if (listener == null) { return; } listenerList.add(listener); Object bean = getBean(); if (bean != null) { BeanUtils.addPropertyChangeListener(bean, listener); } } /** * Removes a PropertyChangeListener from the list of bean listeners. * This method should be used to remove PropertyChangeListeners that * were registered for all bound properties of the target bean.

* * If listener is {@code null}, no exception is thrown and no action is performed. * * @param listener the PropertyChangeListener to be removed * * @see #addPropertyChangeListener(PropertyChangeListener) * @see #addPropertyChangeListener(String, PropertyChangeListener) * @see #removePropertyChangeListener(String, PropertyChangeListener) * @see #getPropertyChangeListeners() */ public synchronized void removePropertyChangeListener( PropertyChangeListener listener) { if (listener == null) { return; } listenerList.remove(listener); Object bean = getBean(); if (bean != null) { BeanUtils.removePropertyChangeListener(bean, listener); } } /** * Adds a PropertyChangeListener to the list of bean listeners for a * specific property. The specified property may be user-defined.

* * Note that if the bean is inheriting a bound property, then no event * will be fired in response to a change in the inherited property.

* * If listener is {@code null}, no exception is thrown and no action is performed. * * @param propertyName one of the property names listed above * @param listener the PropertyChangeListener to be added * * @see #removePropertyChangeListener(String, PropertyChangeListener) * @see #addPropertyChangeListener(String, PropertyChangeListener) * @see #getPropertyChangeListeners(String) */ public synchronized void addPropertyChangeListener( String propertyName, PropertyChangeListener listener) { if (listener == null) { return; } List namedListenerList = namedListeners.get(propertyName); if (namedListenerList == null) { namedListenerList = new ArrayList(); namedListeners.put(propertyName, namedListenerList); } namedListenerList.add(listener); Object bean = getBean(); if (bean != null) { BeanUtils.addPropertyChangeListener(bean, propertyName, listener); } } /** * Removes a PropertyChangeListener from the listener list for a specific * property. This method should be used to remove PropertyChangeListeners * that were registered for a specific bound property.

* * If listener is {@code null}, no exception is thrown and no action is performed. * * @param propertyName a valid property name * @param listener the PropertyChangeListener to be removed * * @see #addPropertyChangeListener(String, PropertyChangeListener) * @see #removePropertyChangeListener(PropertyChangeListener) * @see #getPropertyChangeListeners(String) */ public synchronized void removePropertyChangeListener( String propertyName, PropertyChangeListener listener) { if (listener == null) { return; } List namedListenerList = namedListeners.get(propertyName); if (namedListenerList == null) { return; } namedListenerList.remove(listener); Object bean = getBean(); if (bean != null) { BeanUtils.removePropertyChangeListener(bean, propertyName, listener); } } // Requesting Listener Sets *********************************************** /** * Returns an array of all the property change listeners * registered on this component. * * @return all of this component's PropertyChangeListeners * or an empty array if no property change * listeners are currently registered * * @see #addPropertyChangeListener(PropertyChangeListener) * @see #removePropertyChangeListener(PropertyChangeListener) * @see #getPropertyChangeListeners(String) * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners() */ public synchronized PropertyChangeListener[] getPropertyChangeListeners() { if (listenerList.isEmpty()) { return new PropertyChangeListener[0]; } return listenerList.toArray(new PropertyChangeListener[listenerList.size()]); } /** * Returns an array of all the listeners which have been associated * with the named property. * * @param propertyName the name of the property to lookup listeners * @return all of the PropertyChangeListeners associated with * the named property or an empty array if no listeners have * been added * * @see #addPropertyChangeListener(String, PropertyChangeListener) * @see #removePropertyChangeListener(String, PropertyChangeListener) * @see #getPropertyChangeListeners() */ public synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { List namedListenerList = namedListeners.get(propertyName); if (namedListenerList == null || namedListenerList.isEmpty()) { return new PropertyChangeListener[0]; } return namedListenerList.toArray(new PropertyChangeListener[namedListenerList.size()]); } // Releasing PropertyChangeListeners ************************************** /** * Removes all registered PropertyChangeListeners from * the current target bean - if any. */ public void removeAll() { removeAllListenersFrom(getBean()); } // Changing the Bean & Adding and Removing the PropertyChangeHandlers ***** private void setBean0(Object oldBean, Object newBean) { removeAllListenersFrom(oldBean); addAllListenersTo(newBean); } /** * Adds all registered PropertyChangeListeners from the given bean. * If the bean is null no exception is thrown and no action is taken. * * @param bean the bean to add a the property change listeners to */ private void addAllListenersTo(Object bean) { if (bean == null) { return; } for (PropertyChangeListener listener : listenerList) { BeanUtils.addPropertyChangeListener(bean, listener); } for (Entry> entry : namedListeners.entrySet()) { String propertyName = entry.getKey(); for (PropertyChangeListener listener : entry.getValue()) { BeanUtils.addPropertyChangeListener(bean, propertyName, listener); } } } /** * Removes all registered PropertyChangeListeners from the given bean. * If the bean is null no exception is thrown and no action is taken. * * @param bean the bean to remove the property change handler from. * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully */ private void removeAllListenersFrom(Object bean) { if (bean == null) { return; } for (PropertyChangeListener listener : listenerList) { BeanUtils.removePropertyChangeListener(bean, listener); } for (Entry> entry : namedListeners.entrySet()) { String propertyName = entry.getKey(); for (PropertyChangeListener listener : entry.getValue()) { BeanUtils.removePropertyChangeListener(bean, propertyName, listener); } } } // Helper Classes ********************************************************* /** * Listens to changes of the bean. */ private final class BeanChangeHandler implements PropertyChangeListener { /** * The bean channel's value has been changed. Set the new bean, * remove all listeners from the old bean and add them to the new bean. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { setBean0(evt.getOldValue(), evt.getNewValue()); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/PropertyNotFoundException.java0000644000175000017500000000731611374522114031517 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; /** * A runtime exception that describes that a Java Bean property * could not be found. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ * * @see com.jgoodies.binding.beans.PropertyAdapter */ public final class PropertyNotFoundException extends PropertyException { /** * Constructs a new exception instance with the specified detail message. * The cause is not initialized. * * @param propertyName the name of the property that could not be found * @param bean the Java Bean used to lookup the property */ public PropertyNotFoundException(String propertyName, Object bean) { this(propertyName, bean, null); } /** * Constructs a new exception instance with the specified detail message * and cause. * * @param propertyName the name of the property that could not be found * @param bean the Java Bean used to lookup the property * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. */ public PropertyNotFoundException(String propertyName, Object bean, Throwable cause) { super("Property '" + propertyName + "' not found in bean " + bean, cause); } /** * Constructs a new exception instance with the specified detail message * and cause. * * @param propertyName the name of the property that could not be found * @param beanClass the Java Bean class used to lookup the property * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. */ public PropertyNotFoundException(String propertyName, Class beanClass, Throwable cause) { super("Property '" + propertyName + "' not found in bean class " + beanClass, cause); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/PropertyNotBindableException.java0000644000175000017500000000562611374522114032146 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; /** * A runtime exception that describes problems that arise * when a bound property cannot be bound properly. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ * * @see com.jgoodies.binding.beans.PropertyAdapter */ public final class PropertyNotBindableException extends PropertyException { /** * Constructs a new exception instance with the specified detail message. * The cause is not initialized. * * @param message the detail message which is saved for later retrieval by * the {@link #getMessage()} method. */ public PropertyNotBindableException(String message) { super(message); } /** * Constructs a new exception instance with the specified detail message * and cause. * * @param message the detail message which is saved for later retrieval by * the {@link #getMessage()} method. * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. */ public PropertyNotBindableException(String message, Throwable cause) { super(message, cause); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/DelayedPropertyChangeHandler.java0000644000175000017500000002351111374522114032052 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.Timer; /** * A PropertyChangeListener that is intended to handle property changes * after a specified delay. Useful to defer changes until a stable state * is reached. For example if you look up a persistent object for a selection * in a list. You don't want to find and transport objects that the user * selects temporarily; you want to get only a stable selection. * Or if you want to validate on every key typed, you may delay the * validation until no key has been typed for a given time.

* * If this handler is notified about a property change it stores * the PropertyChangeEvent it has received in #propertyChange, * and starts a Swing Timer that will call #delayedPropertyChange * after a delay. In coalescing mode a previously started timer - if any - * will be stopped before. In other words, the timer is restarted.

* * TODO: Write about the recommended delay time - above the double-click time * and somewhere below a second, e.g. 100ms to 200ms.

* * TODO: Summarize the differences between the DelayedReadValueModel, the * DelayedWriteValueModel, and this DelayedPropertyChangeHandler. * * @author Karsten Lentzsch * @version $Revision: 1.17 $ * * @see com.jgoodies.binding.value.DelayedReadValueModel * @see com.jgoodies.binding.extras.DelayedWriteValueModel * @see javax.swing.Timer * * @since 1.1 */ public abstract class DelayedPropertyChangeHandler implements PropertyChangeListener { /** * The delay in milliseconds used as default in the no-arg constructor. */ public static final int DEFAULT_DELAY = 200; // ms /** * The Timer used to perform the delayed property change. * Started or restarted on every property change. */ private final Timer timer; /** * If {@code true} all pending updates will be coalesced. * In other words, an update will be fired if no updates * have been received for this model's delay. */ private boolean coalesce; /** * Holds the most recent pending PropertyChangeEvent as stored * when this handler is notified about a property change, i. e. * #propertyChange is called. This event will be * used to call #delayedPropertyChange after a delay. */ private PropertyChangeEvent pendingEvt; // Instance Creation ****************************************************** /** * Constructs a DelayedPropertyChangeHandler with a default delay. */ public DelayedPropertyChangeHandler() { this(DEFAULT_DELAY); } /** * Constructs a DelayedPropertyChangeHandler with the specified Timer delay * and the coalesce disabled. * * @param delay the milliseconds to wait before the delayed property change * will be performed * * @throws IllegalArgumentException if the delay is negative */ public DelayedPropertyChangeHandler(int delay) { this(delay, false); } /** * Constructs a DelayedPropertyChangeHandler with the specified Timer delay * and the given coalesce mode. * * @param delay the milliseconds to wait before the delayed property change * will be performed * @param coalesce {@code true} to coalesce all pending changes, * {@code false} to fire changes with the delay when an update * has been received * * @throws IllegalArgumentException if the delay is negative * * @see #setCoalesce(boolean) */ public DelayedPropertyChangeHandler(int delay, boolean coalesce) { this.coalesce = coalesce; this.timer = new Timer(delay, new DelayHandler()); timer.setRepeats(false); } // Accessors ************************************************************** /** * Returns the delay, in milliseconds, that is used to defer value change * notifications. * * @return the delay, in milliseconds, that is used to defer * value change notifications * * @see #setDelay * * @since 1.5 */ public final int getDelay() { return timer.getDelay(); } /** * Sets the delay, in milliseconds, that is used to defer value change * notifications. * * @param delay the delay, in milliseconds, that is used to defer * value change notifications * @see #getDelay * * @throws IllegalArgumentException if the delay is negative * * @since 1.5 */ public final void setDelay(int delay) { timer.setInitialDelay(delay); timer.setDelay(delay); } /** * Returns if this model coalesces all pending changes or not. * * @return {@code true} if all pending changes will be coalesced, * {@code false} if pending changes are fired with a delay * when an update has been received. * * @see #setCoalesce(boolean) */ public final boolean isCoalesce() { return coalesce; } /** * Sets if this model shall coalesce all pending changes or not. * In this case, a change event will be fired first, * if no updates have been received for this model's delay. * If coalesce is {@code false}, a change event will be fired * with this model's delay when an update has been received.

* * The default value is {@code false}.

* * Note that this value is not the #coalesce value * of this model's internal Swing timer. * * @param b {@code true} to coalesce, * {@code false} to fire separate changes */ public final void setCoalesce(boolean b) { coalesce = b; } // Misc ******************************************************************* /** * Stops a running timer. Pending events - if any - are canceled * and won't be delivered to {@code #delayedPropertyChange}. * * @since 1.2 */ public final void stop() { timer.stop(); } /** * Checks and answers whether there are pending events. * * @return {@code true} if there are pending events, {@code false} if not. * * @since 2.0.4 */ public final boolean isPending() { return timer.isRunning(); } /** * This handler has been notified about a change in a bound property. * Stores the given event, then starts a timer that'll call * #delayedPropertyChange after this handler's delay. * If coalescing is enabled, a previously started timer - if any - * if stopped before. In other words, the timer is restarted. * * @param evt the PropertyChangeEvent describing the event source * and the property that has changed */ public final void propertyChange(PropertyChangeEvent evt) { pendingEvt = evt; if (coalesce) { timer.restart(); } else { timer.start(); } } /** * This method gets called after this handler's delay * if a bound property has changed. The event is the * pending event as stored in #propertyChange. * * @param evt the PropertyChangeEvent describing the event source * and the property that has changed */ public abstract void delayedPropertyChange(PropertyChangeEvent evt); // Event Handling ********************************************************* /** * Describes the delayed action to be performed by the timer. */ private final class DelayHandler implements ActionListener { /** * An ActionEvent has been fired by the Timer after its delay. * Invokes #delayedPropertyChange with the pending PropertyChangeEvent, * then stops the timer. */ public void actionPerformed(ActionEvent e) { delayedPropertyChange(pendingEvt); timer.stop(); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/PropertyAccessException.java0000644000175000017500000001313711374522114031162 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import java.beans.PropertyDescriptor; /** * A runtime exception that describes read and write access problems when * getting/setting a Java Bean property. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ * @see com.jgoodies.binding.beans.PropertyAdapter */ public final class PropertyAccessException extends PropertyException { /** * Constructs a new exception instance with the specified detail message * and cause. * * @param message the detail message which is saved for later retrieval by * the {@link #getMessage()} method. * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. */ public PropertyAccessException(String message, Throwable cause) { super(message, cause); } /** * Creates and returns a new PropertyAccessException instance for a failed * read access for the specified bean, property descriptor and cause. * * @param bean the target bean * @param propertyDescriptor describes the bean's property * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. * @return an exception that describes a read access problem */ public static PropertyAccessException createReadAccessException( Object bean, PropertyDescriptor propertyDescriptor, Throwable cause) { String beanType = bean == null ? null : bean.getClass().getName(); String message = "Failed to read an adapted Java Bean property." + "\ncause=" + cause + "\nbean=" + bean + "\nbean type=" + beanType + "\nproperty name=" + propertyDescriptor.getName() + "\nproperty type=" + propertyDescriptor.getPropertyType().getName() + "\nproperty reader=" + propertyDescriptor.getReadMethod(); return new PropertyAccessException(message, cause); } /** * Creates and returns a new PropertyAccessException instance for a failed * write access for the specified bean, value, property descriptor and * cause. * * @param bean the target bean * @param value the value that could not be set * @param propertyDescriptor describes the bean's property * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. * @return an exception that describes a write access problem */ public static PropertyAccessException createWriteAccessException( Object bean, Object value, PropertyDescriptor propertyDescriptor, Throwable cause) { String beanType = bean == null ? null : bean.getClass().getName(); String valueType = value == null ? null : value.getClass().getName(); String message = "Failed to set an adapted Java Bean property." + "\ncause=" + cause + "\nbean=" + bean + "\nbean type=" + beanType + "\nvalue=" + value + "\nvalue type=" + valueType + "\nproperty name=" + propertyDescriptor.getName() + "\nproperty type=" + propertyDescriptor.getPropertyType().getName() + "\nproperty setter=" + propertyDescriptor.getWriteMethod(); return new PropertyAccessException(message, cause); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/ExtendedPropertyChangeSupport.java0000644000175000017500000003273111374522114032346 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListenerProxy; import java.beans.PropertyChangeSupport; /** * Differs from its superclass {@link PropertyChangeSupport} in that it can * check for changed values using {@code #equals} or {@code ==}. * Useful if you want to ensure that a {@code PropertyChangeEvent} is fired * if the old and new value are not the same but if they are equal.

* * The Java * Bean Specification recommends to not throw a * PropertyChangeEvent if the old and new value of a bound * Bean property are equal (see chapter 7.4.4). This can reduce the number * of events fired and helps avoid loops. Nevertheless a bound property * may fire an event if the old and new value are equal.

* * An example for a condition where the identity check {@code ==} * is required and the {@code #equals} test fails is class * {@link com.jgoodies.binding.list.SelectionInList}. If the contained * {@code ListModel} changes its value, an internal listener is removed * from the old value and added to the new value. The listener must be * moved from the old instance to the new instance even if these are equal. * The PropertyChangeSupport doesn't fire a property change event * if such a {@code ListModel} is implemented as a {@link java.util.List}. * This is because instances of {@code List} are equal if and only if * all list members are equal and if they are in the same sequence.

* * This class provides two means to fire an event if the old and new * value are equal but not the same. First, you enable the identity check * in constructor {@link #ExtendedPropertyChangeSupport(Object, boolean)}. * By default all calls to #firePropertyChange will then * check the identity, not the equality. Second, you can invoke * {@link #firePropertyChange(PropertyChangeEvent, boolean)} or * {@link #firePropertyChange(String, Object, Object, boolean)} and * enable or disable the identity check for this call only.

* * TODO: (Issue #5) Use WeakReferences to refer to registered listeners.

* * TODO: (Issue #6) Add an optional check for valid property name * when adding a listener for a specific property.

* * TODO: (Issue #7) Add an optional strict check for existing * property names when firing a named property change.

* * TODO: (Issue #11) Consider adding an option that saves update notifications * if 'checkIdentity' is true but the value types can be compared * safely via #equals, for example Strings, Booleans and Numbers. * * @author Karsten Lentzsch * @version $Revision: 1.15 $ * * @see PropertyChangeSupport * @see PropertyChangeEvent * @see PropertyChangeListener * @see Object#equals(Object) * @see java.util.List#equals(Object) */ public final class ExtendedPropertyChangeSupport extends PropertyChangeSupport { /** * @serial the object to be provided as the "source" for any generated events */ private final Object source; /** * The default setting for the identity check. * Can be overridden by the #firePropertyChange methods * that accept a {@code checkIdentity} parameter. */ private final boolean checkIdentityDefault; // /** // * Describes whether this change support ensures that registered // * PropertyChangeListeners are notified in the event dispatch thread (EDT). // */ // private final boolean ensureNotificationInEDT; // Instance Creation ****************************************************** /** * Constructs an ExtendedPropertyChangeSupport object. * * @param sourceBean The bean to be given as the source for any events. */ public ExtendedPropertyChangeSupport(Object sourceBean) { this(sourceBean, false); } /** * Constructs an ExtendedPropertyChangeSupport object * with the specified default test method for differences between * the old and new property values. * * @param sourceBean The object provided as the source for any generated events. * @param checkIdentityDefault true enables the identity check by default */ public ExtendedPropertyChangeSupport( Object sourceBean, boolean checkIdentityDefault) { super(sourceBean); this.source = sourceBean; this.checkIdentityDefault = checkIdentityDefault; } // /** // * Constructs an ExtendedPropertyChangeSupport object // * with the specified default test method for differences between // * the old and new property values. // * // * @param sourceBean The object provided as the source for any generated events. // * @param checkIdentityDefault true enables the identity check by default // * @param ensureNotificationInEDT if true, this class ensures that // * listeners are notified in the event dispatch thread (EDT) // */ // public ExtendedPropertyChangeSupport( // Object sourceBean, // boolean checkIdentityDefault, // boolean ensureNotificationInEDT) { // super(sourceBean); // this.source = sourceBean; // this.checkIdentityDefault = checkIdentityDefault; // this.ensureNotificationInEDT = ensureNotificationInEDT; // } // Firing Events ********************************************************** /** * Fires the specified PropertyChangeEvent to any registered listeners. * Uses the default test ({@code #equals} vs. {@code ==}) * to determine whether the event's old and new values are different. * No event is fired if old and new value are the same. * * @param evt The PropertyChangeEvent object. * * @see PropertyChangeSupport#firePropertyChange(PropertyChangeEvent) */ @Override public void firePropertyChange(PropertyChangeEvent evt) { firePropertyChange(evt, checkIdentityDefault); } /** * Reports a bound property update to any registered listeners. * Uses the default test ({@code #equals} vs. {@code ==}) * to determine whether the event's old and new values are different. * No event is fired if old and new value are the same. * * @param propertyName The programmatic name of the property * that was changed. * @param oldValue The old value of the property. * @param newValue The new value of the property. * * @see PropertyChangeSupport#firePropertyChange(String, Object, Object) */ @Override public void firePropertyChange( String propertyName, Object oldValue, Object newValue) { firePropertyChange(propertyName, oldValue, newValue, checkIdentityDefault); } /** * Fires an existing PropertyChangeEvent to any registered listeners. * The boolean parameter specifies whether differences between the old * and new value are tested using {@code ==} or {@code #equals}. * No event is fired if old and new value are the same. * * @param evt The PropertyChangeEvent object. * @param checkIdentity true to check differences using {@code ==} * false to use {@code #equals}. */ public void firePropertyChange( final PropertyChangeEvent evt, final boolean checkIdentity) { Object oldValue = evt.getOldValue(); Object newValue = evt.getNewValue(); if (oldValue != null && oldValue == newValue) { return; } // if (!ensureNotificationInEDT || EventQueue.isDispatchThread()) { firePropertyChange0(evt, checkIdentity); // return; // } // EventQueue.invokeLater(new Runnable() { // public void run() { // ExtendedPropertyChangeSupport.this.firePropertyChange0( // evt, // checkIdentity); // } // }); } /** * Reports a bound property update to any registered listeners. * No event is fired if the old and new value are the same. * If checkIdentity is {@code true} an event is fired in all * other cases. If this parameter is {@code false}, an event is fired * if old and new values are not equal. * * @param propertyName The programmatic name of the property * that was changed. * @param oldValue The old value of the property. * @param newValue The new value of the property. * @param checkIdentity true to check differences using {@code ==} * false to use {@code #equals}. */ public void firePropertyChange( final String propertyName, final Object oldValue, final Object newValue, final boolean checkIdentity) { if (oldValue != null && oldValue == newValue) { return; } // if (!ensureNotificationInEDT || EventQueue.isDispatchThread()) { firePropertyChange0(propertyName, oldValue, newValue, checkIdentity); // return; // } // EventQueue.invokeLater(new Runnable() { // public void run() { // ExtendedPropertyChangeSupport.this.firePropertyChange0( // propertyName, // oldValue, // newValue, // checkIdentity); // } // }); } // Helper Code ************************************************************ private void firePropertyChange0(PropertyChangeEvent evt, boolean checkIdentity) { if (checkIdentity) { fireUnchecked(evt); } else { super.firePropertyChange(evt); } } private void firePropertyChange0(String propertyName, Object oldValue, Object newValue, boolean checkIdentity) { if (checkIdentity) { fireUnchecked( new PropertyChangeEvent( source, propertyName, oldValue, newValue)); } else { super.firePropertyChange(propertyName, oldValue, newValue); } } /** * Fires a PropertyChangeEvent to all its listeners without checking via * equals method if the old value is equal to new value. The instance * equality check is done by the calling firePropertyChange method * (to avoid instance creation of the PropertyChangeEvent).

* * If some listeners have been added with a named property, then * {@code PropertyChangeSupport#getPropertyChangeListeners()} returns * an array with a mixture of PropertyChangeListeners * and {@code PropertyChangeListenerProxy}s. We notify all non-proxies * and those proxies that have a property name that is equals to the * event's property name. * * @param evt event to fire to the listeners * * @see PropertyChangeListenerProxy * @see PropertyChangeSupport#getPropertyChangeListeners() */ private void fireUnchecked(PropertyChangeEvent evt) { PropertyChangeListener[] listeners; synchronized (this) { listeners = getPropertyChangeListeners(); } String propertyName = evt.getPropertyName(); for (PropertyChangeListener listener : listeners) { if (listener instanceof PropertyChangeListenerProxy) { PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener; if (proxy.getPropertyName().equals(propertyName)) { proxy.propertyChange(evt); } } else { listener.propertyChange(evt); } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/Observable.java0000644000175000017500000000644211374522114026422 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import java.beans.PropertyChangeListener; /** * Describes objects that provide bound properties as specified in the * Java * Bean Specification. * This interface is primarily intended to ensure compile-time safety * for beans that shall be observed by a {@link BeanAdapter} or * {@link PropertyAdapter}. However, these classes can observe beans * that follow the Bean specification for bound properties * - even if the beans don't implement this Observable interface. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ * * @see PropertyChangeListener * @see java.beans.PropertyChangeEvent * @see java.beans.PropertyChangeSupport * @see BeanAdapter * @see PropertyAdapter */ public interface Observable { /** * Adds the given PropertyChangeListener to the listener list. * The listener is registered for all bound properties of this class. * * @param listener the PropertyChangeListener to be added * * @see #removePropertyChangeListener(PropertyChangeListener) */ void addPropertyChangeListener(PropertyChangeListener listener); /** * Removes the given PropertyChangeListener from the listener list. * This method should be used to remove PropertyChangeListeners that were * registered for all bound properties of this class. * * @param listener the PropertyChangeListener to be removed * * @see #addPropertyChangeListener(PropertyChangeListener) */ void removePropertyChangeListener(PropertyChangeListener listener); } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/PropertyException.java0000644000175000017500000000575611374522114030050 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; /** * A runtime exception that is the abstract superclass for all exceptions * around Java Bean properties in the JGoodies Data Binding framework. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ * * @see com.jgoodies.binding.beans.PropertyAdapter */ public abstract class PropertyException extends RuntimeException { // Instance Creation ****************************************************** /** * Constructs a new exception instance with the specified detail message. * The cause is not initialized. * * @param message the detail message which is saved for later retrieval by * the {@link #getMessage()} method. */ public PropertyException(String message) { this(message, null); } /** * Constructs a new exception instance with the specified detail message * and cause. * * @param message the detail message which is saved for later retrieval by * the {@link #getMessage()} method. * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. */ public PropertyException(String message, Throwable cause) { super(message, cause); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/package.html0000644000175000017500000000625011374522114025751 0ustar twernertwerner Contains classes to model and work with Java Beans and Java Bean properties. At the core of the package is the {@link com.jgoodies.binding.beans.PropertyAdapter} that converts a Java Bean property into a {@link com.jgoodies.binding.value.ValueModel}. This adapter can observe changes of bound bean properties.

The abstract class {@link com.jgoodies.binding.beans.Model} minimizes the effort required to implement Java Beans that provide support for bound properties. The interface {@link com.jgoodies.binding.beans.Observable} ensures compile-time safety for the bound property support; however, it is not required to implement this interface.

Related Documentation

For more information see: @see com.jgoodies.binding @see com.jgoodies.binding.adapter @see com.jgoodies.binding.formatter @see com.jgoodies.binding.list @see com.jgoodies.binding.value jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/Model.java0000644000175000017500000001225111374522114025371 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.VetoableChangeListener; import java.beans.VetoableChangeSupport; import com.jgoodies.common.bean.AbstractBean; /** * An abstract superclass that minimizes the effort required to provide * change support for bound and constrained Bean properties. * This class follows the conventions and recommendations as described * in the Java Bean Specification.

* * Uses class {@link com.jgoodies.binding.beans.ExtendedPropertyChangeSupport}, * to enable the == or #equals test when * changing values.

* * TODO: Consider adding a method #fireChange that invokes * #firePropertyChange if and only if * new value != old value. The background is, that * #firePropertyChange must fire an event * if new value==null==old value.

* * TODO: Consider renaming this class to AbstractBean. That better describes * what this class is about. * * @author Karsten Lentzsch * @version $Revision: 1.20 $ * * @see Observable * @see java.beans.PropertyChangeEvent * @see PropertyChangeListener * @see PropertyChangeSupport * @see ExtendedPropertyChangeSupport * @see VetoableChangeListener * @see VetoableChangeSupport */ public abstract class Model extends AbstractBean implements Observable { // Overriding Superclass Behavior **************************************** /** * Creates and returns a PropertyChangeSupport for the given bean. * Invoked by the first call to {@link #addPropertyChangeListener} * when lazily creating the sole change support instance used throughout * this bean.

* * This implementation creates an extended change support that allows * to configure whether the old and new value are compared with * {@code ==} or {@code equals}. * * @param bean the bean to create a change support for * @return the new change support */ @Override protected PropertyChangeSupport createPropertyChangeSupport(Object bean) { return new ExtendedPropertyChangeSupport(bean); } // Managing Property Change Listeners ********************************** /** * Support for reporting bound property changes for Object properties. * This method can be called when a bound property has changed and it will * send the appropriate PropertyChangeEvent to any registered * PropertyChangeListeners.

* * The boolean parameter specifies whether the old and new value are * compared with {@code ==} or {@code equals}. * * @param propertyName the property whose value has changed * @param oldValue the property's previous value * @param newValue the property's new value * @param checkIdentity true to check differences using {@code ==} * false to use {@code equals}. */ protected final void firePropertyChange( String propertyName, Object oldValue, Object newValue, boolean checkIdentity) { if (changeSupport == null) { return; } ((ExtendedPropertyChangeSupport) changeSupport).firePropertyChange( propertyName, oldValue, newValue, checkIdentity); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/PropertyConnector.java0000644000175000017500000005535111374522114030040 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotBlank; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.*; import com.jgoodies.binding.adapter.BasicComponentFactory; import com.jgoodies.binding.adapter.Bindings; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * Keeps two Java Bean properties in synch. This connector supports bound and * unbound, read-only and read-write properties. Write-only properties are * not supported; connecting two read-only properties won't work; * connecting two unbound properties doesn't make sense.

* * If one of the bean properties fires a property change, this connector * will set the other to the same value. If a bean property is read-only, * the PropertyConnector will not listen to the other bean's property and so * won't update the read-only property. And if a bean does not provide support * for bound properties, it won't be observed. * The properties must be single value bean properties as described by the * Java * Bean Secification.

* * Constraints: the properties must be type compatible, * i. e. values returned by one reader must be accepted by the other's writer, * and vice versa.

* * Examples: * Note that the following examples are for demonstration purposes. * The classes {@link BasicComponentFactory} and {@link Bindings} * provide predefined connections for formatted text fields and combo boxes. * *

 * // Connects a ValueModel and a JFormattedTextField
 * JFormattedTextField textField = new JFormattedTextField();
 * textField.setEditable(editable);
 * PropertyConnector connector =
 *     PropertyConnector.connectAndUpdate(valueModel, "value", textField, "value");
 *
 * // Connects the boolean property "selectable" with a component enablement
 * JComboBox comboBox = new JComboBox();
 * ...
 * PropertyConnector.connect(mainModel, "selectable", comboBox, "enabled");
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.23 $ * * @see PropertyChangeEvent * @see PropertyChangeListener * @see PropertyDescriptor */ public final class PropertyConnector { /** * Holds the first bean that in turn holds the first property. * * @see #getBean1() */ private final Object bean1; /** * Holds the second bean that in turn holds the second property. * * @see #getBean2() */ private final Object bean2; /** * Holds the class used to lookup methods for bean1. * In a future version this may differ from bean1.getClass(). */ private final Class bean1Class; /** * Holds the class used to lookup methods for bean2. * In a future version this may differ from bean2.getClass(). */ private final Class bean2Class; /** * Holds the first property name. * * @see #getProperty1Name() */ private final String property1Name; /** * Holds the second property name. * * @see #getProperty2Name() */ private final String property2Name; /** * The PropertyChangeListener used to handle * changes in the first bean property. */ private final PropertyChangeListener property1ChangeHandler; /** * The PropertyChangeListener used to handle * changes in the second bean property. */ private final PropertyChangeListener property2ChangeHandler; /** * Describes the accessor for property1; basically a getter and setter. */ private final PropertyDescriptor property1Descriptor; /** * Describes the accessor for property1; basically a getter and setter. */ private final PropertyDescriptor property2Descriptor; // Instance creation **************************************************** /** * Constructs a PropertyConnector that synchronizes the two bound * bean properties as specified by the given pairs of bean and associated * property name. * If Bean1#property1Name changes it updates * Bean2#property2Name and vice versa. * If a bean does not provide support for bound properties, * changes will not be observed. * If a bean property is read-only, this connector will not listen to * the other bean's property and so won't update the read-only property. * * @param bean1 the bean that owns the first property * @param property1Name the name of the first property * @param bean2 the bean that owns the second property * @param property2Name the name of the second property * @throws NullPointerException * if a bean or property name is {@code null} * @throws IllegalArgumentException if the beans are identical and * the property name are equal, or if both properties are read-only */ private PropertyConnector(Object bean1, String property1Name, Object bean2, String property2Name) { this.bean1 = checkNotNull(bean1, "Bean1 must not be null."); this.bean2 = checkNotNull(bean2, "Bean2 must not be null."); this.bean1Class = bean1.getClass(); this.bean2Class = bean2.getClass(); this.property1Name = checkNotBlank(property1Name, "PropertyName1 must not be null, empty, or whitespace."); this.property2Name = checkNotBlank(property2Name, "PropertyName2 must not be null, empty, or whitespace."); checkArgument(bean1 != bean2 || !property1Name.equals(property2Name), "Cannot connect a bean property to itself on the same bean."); property1Descriptor = getPropertyDescriptor(bean1Class, property1Name); property2Descriptor = getPropertyDescriptor(bean2Class, property2Name); // Used to check if property2 shall be observed, // i.e. if a listener shall be registered with property2. boolean property1Writable = property1Descriptor.getWriteMethod() != null; boolean property1Readable = property1Descriptor.getReadMethod() != null; // Reject write-only property1 checkArgument(!property1Writable || property1Readable, "Property1 must be readable."); // Used to check if property1 shall be observed, // i.e. if a listener shall be registered with property1. boolean property2Writable = property2Descriptor.getWriteMethod() != null; boolean property2Readable = property2Descriptor.getReadMethod() != null; // Reject write-only property2 checkArgument(!property2Writable || property2Readable, "Property2 must be readable."); // Reject to connect to read-only properties checkArgument(property1Writable || property2Writable, "Cannot connect two read-only properties."); boolean property1Observable = BeanUtils.supportsBoundProperties(bean1Class); boolean property2Observable = BeanUtils.supportsBoundProperties(bean2Class); // We do not reject the case where two unobservable beans // are connected; this allows a hand-update using #updateProperty1 // and #updateProperty2. // Observe property1 if and only if bean1 provides support for // bound bean properties, and if updates can be written to property2. if (property1Observable && property2Writable) { property1ChangeHandler = new PropertyChangeHandler( bean1, property1Descriptor, bean2, property2Descriptor); addPropertyChangeHandler(bean1, bean1Class, property1ChangeHandler); } else { property1ChangeHandler = null; } // Observe property2 if and only if bean2 provides support for // bound bean properties, and if updates can be written to property1. if (property2Observable && property1Writable) { property2ChangeHandler = new PropertyChangeHandler( bean2, property2Descriptor, bean1, property1Descriptor); addPropertyChangeHandler(bean2, bean2Class, property2ChangeHandler); } else { property2ChangeHandler = null; } } /** * Synchronizes the two bound bean properties as specified * by the given pairs of bean and associated property name. * If Bean1#property1Name changes it updates * Bean2#property2Name and vice versa. * If a bean does not provide support for bound properties, * changes will not be observed. * If a bean property is read-only, this connector won't listen to * the other bean's property and so won't update the read-only property.

* * Returns the PropertyConnector that is required if one or the other * property shall be updated. * * @param bean1 the bean that owns the first property * @param property1Name the name of the first property * @param bean2 the bean that owns the second property * @param property2Name the name of the second property * @return the PropertyConnector used to synchronize the properties, * required if property1 or property2 shall be updated * * @throws NullPointerException * if a bean or property name is {@code null} * @throws IllegalArgumentException if the beans are identical and * the property name are equal */ public static PropertyConnector connect(Object bean1, String property1Name, Object bean2, String property2Name) { return new PropertyConnector(bean1, property1Name, bean2, property2Name); } /** * Synchronizes the ValueModel with the specified bound bean property, * and updates the bean immediately. * If the ValueModel changes, it updates Bean2#property2Name * and vice versa. If the bean doesn't provide support for bound properties, * changes will not be observed. * If the bean property is read-only, this connector will not listen * to the ValueModel and so won't update the read-only property. * * @param valueModel the ValueModel that provides a bound value * @param bean2 the bean that owns the second property * @param property2Name the name of the second property * @throws NullPointerException * if the ValueModel, bean or property name is {@code null} * @throws IllegalArgumentException if the bean is the ValueModel * and the property name is "value" * * @since 2.0 */ public static void connectAndUpdate( ValueModel valueModel, Object bean2, String property2Name) { PropertyConnector connector = new PropertyConnector( valueModel, "value", bean2, property2Name); connector.updateProperty2(); } // Property Accessors ***************************************************** /** * Returns the Java Bean that holds the first property. * * @return the Bean that holds the first property */ public Object getBean1() { return bean1; } /** * Returns the Java Bean that holds the first property. * * @return the Bean that holds the first property */ public Object getBean2() { return bean2; } /** * Returns the name of the first Java Bean property. * * @return the name of the first property */ public String getProperty1Name() { return property1Name; } /** * Returns the name of the second Java Bean property. * * @return the name of the second property */ public String getProperty2Name() { return property2Name; } // Sychronization ********************************************************* /** * Reads the value of the second bean property and sets it as new * value of the first bean property. * * @see #updateProperty2() */ public void updateProperty1() { Object property2Value = BeanUtils.getValue(bean2, property2Descriptor); setValueSilently(bean2, property2Descriptor, bean1, property1Descriptor, property2Value); } /** * Reads the value of the first bean property and sets it as new * value of the second bean property. * * @see #updateProperty1() */ public void updateProperty2() { Object property1Value = BeanUtils.getValue(bean1, property1Descriptor); setValueSilently(bean1, property1Descriptor, bean2, property2Descriptor, property1Value); } // Release **************************************************************** /** * Removes the PropertyChangeHandler from the observed bean, * if the bean is not null and if property changes are not observed. * This connector must not be used after calling #release.

* * To avoid memory leaks it is recommended to invoke this method, * if the connected beans live much longer than this connector.

* * As an alternative you may use event listener lists in the connected * beans that are implemented using WeakReference. * * @see java.lang.ref.WeakReference */ public void release() { removePropertyChangeHandler(bean1, bean1Class, property1ChangeHandler); removePropertyChangeHandler(bean2, bean2Class, property2ChangeHandler); } /** * Used to add this class' PropertyChangeHandler to the given bean * if it is not {@code null}. First checks if the bean class * supports bound properties, i.e. it provides a pair of methods * to register multicast property change event listeners; * see section 7.4.1 of the Java Beans specification for details. * * @param bean the bean to add a property change listener * @param listener the property change listener to be added * @throws NullPointerException * if the listener is {@code null} * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be added successfully */ private static void addPropertyChangeHandler( Object bean, Class beanClass, PropertyChangeListener listener) { if (bean != null) { BeanUtils.addPropertyChangeListener(bean, beanClass, listener); } } /** * Used to remove this class' PropertyChangeHandler from the given bean * if it is not {@code null}. * * @param bean the bean to remove the property change listener from * @param listener the property change listener to be removed * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully */ private static void removePropertyChangeHandler( Object bean, Class beanClass, PropertyChangeListener listener) { if (bean != null) { BeanUtils.removePropertyChangeListener(bean, beanClass, listener); } } // Helper Methods to Get and Set a Property Value ************************* private void setValueSilently( Object sourceBean, PropertyDescriptor sourcePropertyDescriptor, Object targetBean, PropertyDescriptor targetPropertyDescriptor, Object newValue) { Object targetValue = BeanUtils.getValue(targetBean, targetPropertyDescriptor); if (targetValue == newValue) { return; } if (property1ChangeHandler != null) { removePropertyChangeHandler(bean1, bean1Class, property1ChangeHandler); } if (property2ChangeHandler != null) { removePropertyChangeHandler(bean2, bean2Class, property2ChangeHandler); } try { // Set the new value in the target bean. BeanUtils.setValue(targetBean, targetPropertyDescriptor, newValue); } catch (PropertyVetoException e) { // Silently ignore this situation here, will be handled below. } // The target bean setter may have modified the new value. // Read the value set in the target bean. targetValue = BeanUtils.getValue(targetBean, targetPropertyDescriptor); // If the new value and the value read differ, // update the source bean's value. // This ignores that the source bean setter may modify the value again. // But we won't end in a loop. if (!Objects.equals(targetValue, newValue)) { boolean sourcePropertyWritable = sourcePropertyDescriptor.getWriteMethod() != null; if (sourcePropertyWritable) { try { BeanUtils.setValue(sourceBean, sourcePropertyDescriptor, targetValue); } catch (PropertyVetoException e) { // Ignore. The value set is a modified variant // of a value that had been accepted before. } } } if (property1ChangeHandler != null) { addPropertyChangeHandler(bean1, bean1Class, property1ChangeHandler); } if (property2ChangeHandler != null) { addPropertyChangeHandler(bean2, bean2Class, property2ChangeHandler); } } /** * Looks up, lazily initializes and returns a PropertyDescriptor * for the given Java Bean and property name. * * @param beanClass the Java Bean class used to lookup the property from * @param propertyName the name of the property * @return the descriptor for the given bean and property name * @throws PropertyNotFoundException if the property could not be found */ private static PropertyDescriptor getPropertyDescriptor( Class beanClass, String propertyName) { try { return BeanUtils.getPropertyDescriptor(beanClass, propertyName); } catch (IntrospectionException e) { throw new PropertyNotFoundException(propertyName, beanClass, e); } } /** * Listens to changes of a bean property and updates the property. */ private final class PropertyChangeHandler implements PropertyChangeListener { /** * Holds the bean that sends updates. */ private final Object sourceBean; /** * Holds the property descriptor for the bean to read from. */ private final PropertyDescriptor sourcePropertyDescriptor; /** * Holds the bean to update. */ private final Object targetBean; /** * Holds the property descriptor for the bean to update. */ private final PropertyDescriptor targetPropertyDescriptor; private PropertyChangeHandler( Object sourceBean, PropertyDescriptor sourcePropertyDescriptor, Object targetBean, PropertyDescriptor targetPropertyDescriptor) { this.sourceBean = sourceBean; this.sourcePropertyDescriptor = sourcePropertyDescriptor; this.targetBean = targetBean; this.targetPropertyDescriptor = targetPropertyDescriptor; } /** * A property in the observed bean has changed. First checks, * if this listener should handle the event, because the event's * property name is the one to be observed or the event indicates * that any property may have changed. In case the event provides * no new value, it is read from the source bean. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { String sourcePropertyName = sourcePropertyDescriptor.getName(); String propertyName = evt.getPropertyName(); if (propertyName == null || propertyName.equals(sourcePropertyName)) { Object newValue = evt.getNewValue(); if (newValue == null || propertyName == null) { newValue = BeanUtils.getValue(sourceBean, sourcePropertyDescriptor); } setValueSilently(sourceBean, sourcePropertyDescriptor, targetBean, targetPropertyDescriptor, newValue); } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/BeanAdapter.java0000644000175000017500000017335511374522114026514 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotBlank; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyDescriptor; import java.beans.PropertyVetoException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * Converts multiple Java Bean properties into ValueModels. * The bean properties must be single valued properties as described by the * Java * Bean Specification. See below for a comparison with the more frequently * used PresentationModel class and the rarely used PropertyAdapter.

* * ValueModels can be created for a property name using * {@link #getValueModel(String)} or for a triple of (property name, getter * name, setter name) using {@link #getValueModel(String, String, String)}. * If you just specify the property name, the adapter uses the standard * Java Bean introspection to lookup the available properties and how to * read and write the property value. In case of custom readers and writers * you may specify a custom BeanInfo class, or as a shortcut use the method * that accepts the optional getter and setter name. If these are specified, * introspection will be bypassed and a PropertyDescriptor will be * created for the given property name, getter and setter name. * Note: For each property name subsequent calls * to these methods must use the same getter and setter names. Attempts * to violate this constraint are rejected with an IllegalArgumentException.

* * Property values for a given property name can be read using * {@link #getValue(String)}. To set a value for a for a property name * invoke {@link #setValue(String, Object)}.

* * Optionally the BeanAdapter can observe changes in bound * properties as described in section 7.4 of the Bean specification. * The bean then must provide support for listening on properties as described * in section 7.4 of this specification. * You can enable this feature by setting the constructor parameter * observeChanges to {@code true}. * If the adapter observes changes, the ValueModels returned by * #getValueModel will fire value change events, * i.e. PropertyChangeEvents for the property "value". * Even if you ignore property changes, you can access the adapted * property value via #getValue(). * It's just that you won't be notified about changes.

* * In addition you can observe the bean's bound properties * by registering PropertyChangeListeners with the bean using * #addBeanPropertyChangeListener. These listeners will be removed * from the old bean before the bean changes and will be re-added after * the new bean has been set. Therefore these listeners will be notified * about changes only if the current bean changes a property. They won't be * notified if the bean changes - and in turn the property value. If you want * to observes property changes caused by bean changes too, register with the * adapting ValueModel as returned by #getValueModel(String).

* * The BeanAdapter provides two access styles to the target bean * that holds the adapted property: you can specify a bean directly, * or you can use a bean channel to access the bean indirectly. * In the latter case you specify a ValueModel * that holds the bean that in turn holds the adapted properties.

* * If the adapted bean is {@code null} the BeanAdapter can * neither read nor set a value. In this case #getValue * returns {@code null} and #setValue will silently * ignore the new value.

* * This adapter throws three PropertyChangeEvents if the bean changes: * beforeBean, bean and afterBean. This is useful * when sharing a bean channel and you must perform an operation before * or after other listeners handle a bean change. Since you cannot rely * on the order listeners will be notified, only the beforeBean * and afterBean events are guaranteed to be fired before and * after the bean change is fired. * Note that #getBean() returns the new bean before * any of these three PropertyChangeEvents is fired. Therefore listeners * that handle these events must use the event's old and new value * to determine the old and new bean. * The order of events fired during a bean change is:

    *
  1. this adapter's bean channel fires a value change, *
  2. this adapter fires a beforeBean change, *
  3. this adapter fires the bean change, *
  4. this adapter fires an afterBean change. *

* * Note: * BeanAdapters that observe changes have a PropertyChangeListener * registered with the target bean. Hence, a bean has a reference * to any BeanAdapter that observes it. To avoid memory leaks * it is recommended to remove this listener if the bean lives much longer * than the BeanAdapter, enabling the garbage collector to remove the adapter. * To do so, you can call setBean(null) or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with WeakReference.

* * Setting the bean to null has side-effects, for example the adapter * fires a change event for the bound property bean and other properties. * And the value of ValueModel's vended by this adapter may change. * However, typically this is fine and setting the bean to null * is the first choice for removing the reference from the bean to the adapter. * Another way to clear the reference from the target bean is * to call #release. It has no side-effects, but the adapter * must not be used anymore once #release has been called.

* * Constraints: If property changes shall be observed, * the bean class must support bound properties, i. e. it must provide * the following pair of methods for registration of multicast property * change event listeners: *

 * public void addPropertyChangeListener(PropertyChangeListener x);
 * public void removePropertyChangeListener(PropertyChangeListener x);
 * 
* * PropertyAdapter vs. BeanAdapter vs. PresentationModel
* Basically the BeanAdapter does for multiple properties what the * {@link com.jgoodies.binding.beans.PropertyAdapter} does for a * single bean property. * If you adapt multiple properties of the same bean, you better use * the BeanAdapter. It registers a single PropertyChangeListener with the bean, * where multiple PropertyAdapters would register multiple listeners. * If you adapt bean properties for an editor, you will typically use the * {@link com.jgoodies.binding.PresentationModel}. The PresentationModel is * more powerful than the BeanAdapter. It adds support for buffered models, * and provides an extensible mechanism for observing the change state * of the bean and related objects.

* * Basic Examples: *

 * // Direct access, ignores changes
 * Address address = new Address()
 * BeanAdapter adapter = new BeanAdapter(address);
 * adapter.setValue("street", "Broadway");
 * System.out.println(address.getStreet());        // Prints "Broadway"
 * address.setStreet("Franz-Josef-Str.");
 * System.out.println(adapter.getValue("street")); // Prints "Franz-Josef-Str."
 *
 *
 * //Direct access, observes changes
 * BeanAdapter adapter = new BeanAdapter(address, true);
 *
 *
 * // Indirect access, ignores changes
 * ValueHolder addressHolder = new ValueHolder(address1);
 * BeanAdapter adapter = new BeanAdapter(addressHolder);
 * adapter.setValue("street", "Broadway");         // Sets the street in address1
 * System.out.println(address1.getStreet());       // Prints "Broadway"
 * adapter.setBean(address2);
 * adapter.setValue("street", "Robert-Koch-Str."); // Sets the street in address2
 * System.out.println(address2.getStreet());       // Prints "Robert-Koch-Str."
 *
 *
 * // Indirect access, observes changes
 * ValueHolder addressHolder = new ValueHolder();
 * BeanAdapter adapter = new BeanAdapter(addressHolder, true);
 * addressHolder.setValue(address1);
 * address1.setStreet("Broadway");
 * System.out.println(adapter.getValue("street")); // Prints "Broadway"
 *
 *
 * // Access through ValueModels
 * Address address = new Address();
 * BeanAdapter adapter = new BeanAdapter(address);
 * ValueModel streetModel = adapter.getValueModel("street");
 * ValueModel cityModel   = adapter.getValueModel("city");
 * streetModel.setValue("Broadway");
 * System.out.println(address.getStreet());        // Prints "Broadway"
 * address.setCity("Hamburg");
 * System.out.println(cityModel.getValue());       // Prints "Hamburg"
 * 
* * Adapter Chain Example: *
Builds an adapter chain from a domain model to the presentation layer. *
 * Country country = new Country();
 * country.setName("Germany");
 * country.setEuMember(true);
 *
 * BeanAdapter countryAdapter = new BeanAdapter(country, true);
 *
 * JTextField nameField = new JTextField();
 * nameField.setDocument(new DocumentAdapter(
 *     countryAdapter.getValueModel("name")));
 *
 * JCheckBox euMemberBox = new JCheckBox("Is EU Member");
 * euMemberBox.setModel(new ToggleButtonAdapter(
 *      countryAdapter.getValueModel("euMember")));
 *
 * // Using factory methods
 * JTextField nameField   = Factory.createTextField(country, "name");
 * JCheckBox  euMemberBox = Factory.createCheckBox (country, "euMember");
 * euMemberBox.setText("Is EU Member");
 * 

* * As of version 1.3 this class is no longer marked as final, * but lacks the documentation for subclass constraints. * I plan to introduce an interface that shall describe the semantics * required by the PresentationModel class. Until then, this BeanAdapter * implementation describes the semantics and all constraints.

* * TODO: Improve the class comment and focus on the main features.

* * TODO: Consider adding a feature to ensure that update notifications * are performed in the event dispatch thread. In case the adapted bean * is changed in a thread other than the event dispatch thread, such * a feature would help complying with Swing's single thread rule. * The feature could be implemented by an extended PropertyChangeSupport.

* * TODO: I plan to improve the support for adapting beans that do not fire * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter, * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's * internal SimplePropertyAdapter's shall be able to optionally self-fire * a PropertyChangeEvent in case the bean does not. There are several * downsides with self-firing events compared to bound bean properties. * See Issue * 49 for more information about the downsides.

* * The observeChanges constructor parameter shall be replaced by a more * fine-grained choice to not observe (former observeChanges=false), * to observe bound properties (former observeChanges=true), and a new * setting for self-firing PropertyChangeEvents if a value is set. * The latter case may be further splitted up to specify how the * self-fired PropertyChangeEvent is created: *

    *
  1. oldValue=null, newValue=null *
  2. oldValue=null, newValue=the value set *
  3. oldValue=value read before the set, newValue=the value set *
  4. oldValue=value read before the set, newValue=value read after the set *
* * @author Karsten Lentzsch * @version $Revision: 1.34 $ * * @see com.jgoodies.binding.beans.PropertyAdapter * @see ValueModel * @see ValueModel#getValue() * @see ValueModel#setValue(Object) * @see PropertyChangeEvent * @see PropertyChangeListener * @see java.beans.Introspector * @see java.beans.BeanInfo * @see PropertyDescriptor * * @param the type of the bean managed by this BeanAdapter */ public class BeanAdapter extends Model { /** * The property name used in the PropertyChangeEvent that is fired * before the bean property fires its PropertyChangeEvent. * Useful to perform an operation before listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean"; /** * The name of the read-write bound property that holds the target bean. * * @see #getBean() * @see #setBean(Object) */ public static final String PROPERTYNAME_BEAN = "bean"; /** * The property name used in the PropertyChangeEvent that is fired * after the bean property fires its PropertyChangeEvent. * Useful to perform an operation after listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTYNAME_AFTER_BEAN = "afterBean"; /** * The name of the read-only bound bean property that * indicates whether one of the observed properties has changed. * * @see #isChanged() */ public static final String PROPERTYNAME_CHANGED = "changed"; // Fields ***************************************************************** /** * Holds a ValueModel that holds the bean * that in turn holds the adapted property. * * @see #getBean() * @see #setBean(Object) */ private final ValueModel beanChannel; /** * Specifies whether we observe property changes and in turn * fire state changes. * * @see #getObserveChanges() */ private final boolean observeChanges; /** * Maps property names to the associated SimplePropertyAdapters. * * @see #getValueModel(String) * @see #getValueModel(String, String, String) */ private final Map propertyAdapters; /** * Refers to the IndirectPropertyChangeSupport that is used to redirect * PropertyChangelisteners to the current target bean. */ private IndirectPropertyChangeSupport indirectChangeSupport; /** * Refers to the old bean. Used as old value if the bean changes. * Updated after a bean change in the BeanChangeHandler. */ B storedOldBean; /** * Indicates whether a property in the current target been has changed. * Will be reset to {@code false} every time the target bean changes. * * @see #isChanged() * @see #setBean(Object) */ private boolean changed = false; /** * The PropertyChangeListener used to handle changes * in the bean properties. A new instance is created every time * the target bean changes. */ private PropertyChangeListener propertyChangeHandler; // Instance creation **************************************************** /** * Constructs a BeanAdapter for the given bean; * does not observe changes.

* * Installs a default bean channel that checks the identity not equity * to ensure that listeners are reregistered properly if the old and * new bean are equal but not the same. * * @param bean the bean that owns the properties to adapt */ public BeanAdapter(B bean) { this(bean, false); } /** * Constructs a BeanAdapter for the given bean; * observes changes if specified.

* * Installs a default bean channel that checks the identity not equity * to ensure that listeners are reregistered properly if the old and * new bean are equal but not the same. * * @param bean the bean that owns the properties to adapt * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * @throws PropertyUnboundException if observeChanges * is true but the property is unbound, i. e. the bean * does not provide a pair of methods to register a multicast * PropertyChangeListener */ public BeanAdapter( B bean, boolean observeChanges) { this(new ValueHolder(bean, true), observeChanges); } /** * Constructs a BeanAdapter for the given bean channel; * does not observe changes.

* * It is strongly recommended that the bean channel checks the identity * not equity. This ensures that listeners are reregistered properly if * the old and new bean are equal but not the same. * * @param beanChannel the ValueModel that holds the bean */ public BeanAdapter(ValueModel beanChannel) { this(beanChannel, false); } /** * Constructs a BeanAdapter for the given bean channel; * observes changes if specified.

* * It is strongly recommended that the bean channel checks the identity * not equity. This ensures that listeners are reregistered properly if * the old and new bean are equal but not the same. * * @param beanChannel the ValueModel that holds the bean * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * * @throws IllegalArgumentException if the beanChannel is a ValueHolder * that has the identityCheck feature disabled * @throws PropertyUnboundException if observeChanges * is true but the property is unbound, i. e. the bean * does not provide a pair of methods to register a multicast * PropertyChangeListener */ public BeanAdapter( ValueModel beanChannel, boolean observeChanges) { this.beanChannel = beanChannel != null ? beanChannel : new ValueHolder(null, true); checkBeanChannelIdentityCheck(beanChannel); this.observeChanges = observeChanges; this.propertyAdapters = new HashMap(); this.beanChannel.addValueChangeListener(new BeanChangeHandler()); B initialBean = getBean(); if (initialBean != null) { if (observeChanges && !BeanUtils.supportsBoundProperties(getBeanClass(initialBean))) { throw new PropertyUnboundException( "The bean must provide support for listening on property changes " + "as described in section 7.4.5 of the Java Bean Specification."); } addChangeHandlerTo(initialBean); } storedOldBean = initialBean; } // Accessors ************************************************************ /** * Returns the ValueModel that holds the bean that in turn holds * the adapted properties. This bean channel is shared by the * PropertyAdapters created by the factory method * #getValueModel. * * @return the ValueModel that holds the bean that in turn * holds the adapted properties * * @see #getBean() * @see #setBean(Object) * * @since 1.3 */ public ValueModel getBeanChannel() { return beanChannel; } /** * Returns the Java Bean that holds the adapted properties. * * @return the Bean that holds the adapted properties * * @see #setBean(Object) */ public B getBean() { return (B) beanChannel.getValue(); } /** * Sets a new Java Bean as holder of the adapted properties. * Notifies any registered value listeners that are registered * with the adapting ValueModels created in #getValueModel. * Also notifies listeners that have been registered with this adapter * to observe the bound property bean.

* * Resets the changed state to {@code false}.

* * If this adapter observes bean changes, the bean change handler * will be removed from the former bean and will be added to the new bean. * Hence, if the new bean is {@code null}, this adapter has no * listener registered with a bean. * And so, setBean(null) can be used as a clean release method * that allows to use this adapter later again. * * @param newBean the new holder of the adapted properties * * @see #getBean() * @see #isChanged() * @see #resetChanged() * @see #release() */ public void setBean(B newBean) { beanChannel.setValue(newBean); resetChanged(); } /** * Answers whether this adapter observes changes in the * adapted Bean properties. * * @return true if this adapter observes changes, false if not */ public boolean getObserveChanges() { return observeChanges; } /* * Sets whether changes in the bean's bound property shall be observed. * As a requirement the bean must provide support for listenening * on property changes. * * @param newValue true to observe changes, false to ignore them public void setObserveChanges(boolean newValue) { if (newValue == getObserveChanges()) return; observeChanges = newValue; Object bean = getBean(); removePropertyChangeHandler(bean); addPropertyChangeHandler(bean); } */ // Accessing Property Values ********************************************** /** * Returns the value of specified bean property, {@code null} * if the current bean is {@code null}.

* * This operation is supported only for readable bean properties. * * @param propertyName the name of the property to be read * @return the value of the adapted bean property, null if the bean is null * * @throws NullPointerException if propertyName is null * @throws UnsupportedOperationException if the property is write-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the value could not be read */ public Object getValue(String propertyName) { return getValueModel(propertyName).getValue(); } /** * Sets the given new value for the specified bean property. Does nothing * if this adapter's bean is {@code null}. If the setter associated * with the propertyName throws a PropertyVetoException, it is silently * ignored.

* * Notifies the associated value change listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal.

* * This operation is supported only for writable bean properties. * * @param propertyName the name of the property to set * @param newValue the value to set * * @throws NullPointerException if propertyName is null * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set */ public void setValue(String propertyName, Object newValue) { getValueModel(propertyName).setValue(newValue); } /** * Sets a new value for the specified bean property. Does nothing if the * bean is {@code null}. If the setter associated with the propertyName * throws a PropertyVetoException, this methods throws the same exception.

* * Notifies the associated value change listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal.

* * This operation is supported only for writable bean properties. * * @param propertyName the name of the property to set * @param newValue the value to set * * @throws NullPointerException if propertyName is null * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set * @throws PropertyVetoException if the bean setter * throws a PropertyVetoException * * @since 1.1 */ public void setVetoableValue(String propertyName, Object newValue) throws PropertyVetoException { getValueModel(propertyName).setVetoableValue(newValue); } // Creating and Accessing Adapting ValueModels **************************** /** * Looks up and lazily creates a ValueModel that adapts * the bound property with the specified name. Uses the * Bean introspection to look up the getter and setter names.

* * Subsequent calls to this method with the same property name * return the same ValueModel.

* * To prevent potential runtime errors this method eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method and to * #getValueModel(String, String, String) must use * the same getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially once * you've called this method you must not call * #getValueModel(String, String, String) with a non-null * getter or setter name. And vice versa, once you've called the latter * method with a non-null getter or setter name, you must not call * this method.

* * This method uses a return type of AbstractValueModel, not a ValueModel. * This makes {@link #setVetoableValue(String, Object)} visible. It also * makes the AbstractValueModel convenience type converters available, * which can significantly shrink the source code necessary to read and * write values from/to these models. * * @param propertyName the name of the property to adapt * @return a ValueModel that adapts the property with the specified name * * @throws NullPointerException if propertyName is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException * if #getValueModel(String, String, String) has been * called before with the same property name and a non-null getter * or setter name */ public SimplePropertyAdapter getValueModel(String propertyName) { return getValueModel(propertyName, null, null); } /** * Looks up and lazily creates a ValueModel that adapts the bound property * with the specified name. Unlike #getValueModel(String) * this method bypasses the Bean Introspection and uses the given getter * and setter names to setup the access to the adapted Bean property.

* * Subsequent calls to this method with the same parameters * will return the same ValueModel.

* * To prevent potential runtime errors this method eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method * and to #getValueModel(String) must use the same * getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially * once you've called this method with a non-null getter or setter name, * you must not call #getValueModel(String). And vice versa, * once you've called the latter method you must not call this method * with a non-null getter or setter name.

* * This method uses a return type of AbstractValueModel, not a ValueModel. * This makes {@link #setVetoableValue(String, Object)} visible. It also * makes the AbstractValueModel convenience type converters available, * which can significantly shrink the source code necessary to read and * write values from/to these models. * * @param propertyName the name of the property to adapt * @param getterName the name of the method that reads the value * @param setterName the name of the method that sets the value * @return a ValueModel that adapts the property with the specified name * * @throws NullPointerException if propertyName is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException if this method has been called before * with the same property name and different getter or setter names */ public SimplePropertyAdapter getValueModel(String propertyName, String getterName, String setterName) { checkNotBlank(propertyName, "The property name must not be null, empty, or whitespace."); SimplePropertyAdapter adaptingModel = getPropertyAdapter(propertyName); if (adaptingModel == null) { adaptingModel = createPropertyAdapter(propertyName, getterName, setterName); propertyAdapters.put(propertyName, adaptingModel); } else { checkArgument(Objects.equals(getterName, adaptingModel.getterName) && Objects.equals(setterName, adaptingModel.setterName), "You must not invoke this method twice " + "with different getter and/or setter names."); } return adaptingModel; } /** * Looks up and returns the SimplePropertyAdapter that adapts * the bound property with the specified name. * * @param propertyName the name of the adapted property * @return a SimplePropertyAdapter that adapts the bound property * with the specified name or null, if none has been created before */ SimplePropertyAdapter getPropertyAdapter(String propertyName) { return propertyAdapters.get(propertyName); } /** * Creates and returns a SimplePropertyAdapter that adapts * the bound property with the specified name. * * @param propertyName the name of the property to adapt * @param getterName the name of the method that reads the value * @param setterName the name of the method that sets the value * @return a SimplePropertyAdapter that adapts the property * with the specified name * * @since 1.4 */ protected SimplePropertyAdapter createPropertyAdapter( String propertyName, String getterName, String setterName) { return new SimplePropertyAdapter(propertyName, getterName, setterName); } // Accessing the Changed State ******************************************** /** * Answers whether a bean property has changed since the changed state * has been reset. The changed state is implicitly reset every time * the target bean changes. * * @return true if a property of the current target bean * has changed since the last reset */ public boolean isChanged() { return changed; } /** * Resets this tracker's changed state to {@code false}. */ public void resetChanged() { setChanged(false); } /** * Sets the changed state to the given value. Invoked by the global * PropertyChangeHandler that observes all bean changes. Also invoked * by #resetChanged. * * @param newValue the new changed state */ private void setChanged(boolean newValue) { boolean oldValue = isChanged(); changed = newValue; firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue); } // Managing Bean Property Change Listeners ******************************* /** * Adds a PropertyChangeListener to the list of bean listeners. The * listener is registered for all bound properties of the target bean.

* * The listener will be notified if and only if this BeanAdapter's current * bean changes a property. It'll not be notified if the bean changes.

* * If listener is {@code null}, no exception is thrown and no action * is performed. * * @param listener the PropertyChangeListener to be added * * @see #removeBeanPropertyChangeListener(PropertyChangeListener) * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners() */ public synchronized void addBeanPropertyChangeListener( PropertyChangeListener listener) { if (listener == null) { return; } if (indirectChangeSupport == null) { indirectChangeSupport = new IndirectPropertyChangeSupport(beanChannel); } indirectChangeSupport.addPropertyChangeListener(listener); } /** * Removes a PropertyChangeListener from the list of bean listeners. * This method should be used to remove PropertyChangeListeners that * were registered for all bound properties of the target bean.

* * If listener is {@code null}, no exception is thrown and no action is performed. * * @param listener the PropertyChangeListener to be removed * * @see #addBeanPropertyChangeListener(PropertyChangeListener) * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners() */ public synchronized void removeBeanPropertyChangeListener( PropertyChangeListener listener) { if (listener == null || indirectChangeSupport == null) { return; } indirectChangeSupport.removePropertyChangeListener(listener); } /** * Adds a PropertyChangeListener to the list of bean listeners for a * specific property. The specified property may be user-defined.

* * The listener will be notified if and only if this BeanAdapter's * current bean changes the specified property. It'll not be notified * if the bean changes. If you want to observe property changes and * bean changes, you may observe the ValueModel that adapts this property * - as returned by #getValueModel(String).

* * Note that if the bean is inheriting a bound property, then no event * will be fired in response to a change in the inherited property.

* * If listener is {@code null}, no exception is thrown and no action is performed. * * @param propertyName one of the property names listed above * @param listener the PropertyChangeListener to be added * * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners(String) */ public synchronized void addBeanPropertyChangeListener( String propertyName, PropertyChangeListener listener) { if (listener == null) { return; } if (indirectChangeSupport == null) { indirectChangeSupport = new IndirectPropertyChangeSupport(beanChannel); } indirectChangeSupport.addPropertyChangeListener(propertyName, listener); } /** * Removes a PropertyChangeListener from the listener list for a specific * property. This method should be used to remove PropertyChangeListeners * that were registered for a specific bound property.

* * If listener is {@code null}, no exception is thrown and no action is performed. * * @param propertyName a valid property name * @param listener the PropertyChangeListener to be removed * * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #removeBeanPropertyChangeListener(PropertyChangeListener) * @see #getBeanPropertyChangeListeners(String) */ public synchronized void removeBeanPropertyChangeListener( String propertyName, PropertyChangeListener listener) { if (listener == null || indirectChangeSupport == null) { return; } indirectChangeSupport.removePropertyChangeListener(propertyName, listener); } // Requesting Listener Sets *********************************************** /** * Returns an array of all the property change listeners * registered on this component. * * @return all of this component's PropertyChangeListeners * or an empty array if no property change * listeners are currently registered * * @see #addBeanPropertyChangeListener(PropertyChangeListener) * @see #removeBeanPropertyChangeListener(PropertyChangeListener) * @see #getBeanPropertyChangeListeners(String) * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners() */ public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners() { if (indirectChangeSupport == null) { return new PropertyChangeListener[0]; } return indirectChangeSupport.getPropertyChangeListeners(); } /** * Returns an array of all the listeners which have been associated * with the named property. * * @param propertyName the name of the property to lookup listeners * @return all of the PropertyChangeListeners associated with * the named property or an empty array if no listeners have * been added * * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners() */ public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners(String propertyName) { if (indirectChangeSupport == null) { return new PropertyChangeListener[0]; } return indirectChangeSupport.getPropertyChangeListeners(propertyName); } // Releasing PropertyChangeListeners ************************************** /** * Removes the PropertyChangeHandler from the observed bean, if the bean * is not {@code null} and if bean property changes are observed. * Also removes all listeners from the bean that have been registered * with {@code #addBeanPropertyChangeListener} before.

* * BeanAdapters that observe changes have a PropertyChangeListener * registered with the target bean. Hence, a bean has a reference * to all BeanAdapters that observe it. To avoid memory leaks * it is recommended to remove this listener if the bean lives much longer * than the BeanAdapter, enabling the garbage collector to remove the adapter. * To do so, you can call {@code setBean(null)} or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with WeakReference.

* * Setting the bean to null has side-effects, for example the adapter * fires a change event for the bound property bean and other properties. * And the value of ValueModel's vended by this adapter may change. * However, typically this is fine and setting the bean to null * is the first choice for removing the reference from the bean to the adapter. * Another way to clear the reference from the target bean is * to call #release; it has no side-effects.

* * Since version 2.0.4 it is safe to call this method multiple times, * however, the adapter must not be used anymore once #release * has been called. * * @see #setBean(Object) * @see java.lang.ref.WeakReference */ public synchronized void release() { removeChangeHandlerFrom(getBean()); if (indirectChangeSupport != null) { indirectChangeSupport.removeAll(); } } // Changing the Bean & Adding and Removing the PropertyChangeHandler ****** private void setBean0(B oldBean, B newBean) { firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean, true); removeChangeHandlerFrom(oldBean); forwardAllAdaptedValuesChanged(oldBean, newBean); resetChanged(); addChangeHandlerTo(newBean); firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean, true); firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean, true); } /** * Iterates over all internal property adapters to notify them * about a bean change from oldBean to newBean. These adapters then notify * their observers to inform them about a value change - if any.

* * Iterates over a copy of the property adapters to avoid * ConcurrentModifications that may be thrown if a listener creates * a new SimplePropertyAdapter by requesting an adapting model using * #getValueModel. * * @param oldBean the bean before the change * @param newBean the bean after the change */ private void forwardAllAdaptedValuesChanged(B oldBean, B newBean) { Object[] adapters = propertyAdapters.values().toArray(); for (Object adapter : adapters) { ((SimplePropertyAdapter) adapter).setBean0(oldBean, newBean); } } /** * Iterates over all internal property adapters to notify them * about a value change in the bean. These adapters then notify * their observers to inform them about a value change - if any.

* * Iterates over a copy of the property adapters to avoid * ConcurrentModifications that may be thrown if a listener creates * a new SimplePropertyAdapter by requesting an adapting model using * #getValueModel. */ private void forwardAllAdaptedValuesChanged() { B currentBean = getBean(); Object[] adapters = propertyAdapters.values().toArray(); for (Object adapter : adapters) { ((SimplePropertyAdapter) adapter).fireChange(currentBean); } } /** * Adds a property change listener to the given bean if we observe changes * and the bean is not null. First checks whether the bean class * supports bound properties, i.e. it provides a pair of methods * to register multicast property change event listeners; * see section 7.4.1 of the Java Beans specification for details. * * @param bean the bean to add a property change handler. * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be added successfully * * @see #removeChangeHandlerFrom(Object) */ private void addChangeHandlerTo(B bean) { if (!observeChanges || bean == null) { return; } propertyChangeHandler = new PropertyChangeHandler(); BeanUtils.addPropertyChangeListener(bean, getBeanClass(bean), propertyChangeHandler); } /** * Removes the formerly added property change handler from the given bean * if we observe changes and the bean is not null and we haven't called * this method before. * * @param bean the bean to remove the property change handler from. * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully * * @see #addChangeHandlerTo(Object) */ private void removeChangeHandlerFrom(B bean) { if (!observeChanges || bean == null || propertyChangeHandler == null) { return; } BeanUtils.removePropertyChangeListener(bean, getBeanClass(bean), propertyChangeHandler); propertyChangeHandler = null; } // Helper Methods to Get and Set a Property Value ************************* /** * Returns the Java Bean class used by this adapter. * The current implementation just returns the given bean's class.

* * A future version may return a type other than the concrete * class of the given bean. This beanClass could be specified * in a new set of constructors. This is useful if the beans * are specified by public interfaces, and implemented by * package private classes. In this case, the class of the given bean * object shall be checked against the specified type. * * @param bean the bean that may be used to lookup the class from * @return the Java Bean class used for this adapter. */ private Class getBeanClass(B bean) { return bean.getClass(); // TODO: A future version shall add a check like // beanClass.isInstance(bean) if the beanClass // has been specified in the constructor. } /** * Returns the value of the specified property of the given bean, * {@code null} if the bean is {@code null}. * * @param bean the bean to read the value from * @param propertyDescriptor describes the property to be read * @return the bean's property value */ private Object getValue0(B bean, PropertyDescriptor propertyDescriptor) { return bean == null ? null : BeanUtils.getValue(bean, propertyDescriptor); } /** * Sets the given object as new value of the specified property of the * given bean. Does nothing if the bean is null. This write operation is * supported only for writable bean properties. * * @param bean the bean that holds the adapted property * @param propertyDescriptor describes the property to be set * @param newValue the property value to be set * * @throws NullPointerException if the bean is null * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the write access failed * @throws PropertyVetoException if the invoked bean setter * throws a PropertyVetoException */ private void setValue0(B bean, PropertyDescriptor propertyDescriptor, Object newValue) throws PropertyVetoException { BeanUtils.setValue(bean, propertyDescriptor, newValue); } /** * Throws an IllegalArgumentException if the given ValueModel * is a ValueHolder that has the identityCheck feature disabled. */ private void checkBeanChannelIdentityCheck(ValueModel valueModel) { if (!(valueModel instanceof ValueHolder)) { return; } ValueHolder valueHolder = (ValueHolder) valueModel; if (!valueHolder.isIdentityCheckEnabled()) { throw new IllegalArgumentException( "The bean channel must have the identity check enabled."); } } // Helper Classes ********************************************************* /** * Listens to changes of the bean. */ private final class BeanChangeHandler implements PropertyChangeListener { /** * The bean has been changed. Uses the stored old bean instead of * the event's old value, because the latter can be null. * If the event's new value is null, the new bean is requested * from the bean channel. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { B newBean = evt.getNewValue() != null ? (B) evt.getNewValue() : getBean(); setBean0(storedOldBean, newBean); storedOldBean = newBean; } } /** * Listens to changes of all bean properties. Fires property changes * if the associated property or an arbitrary set of properties has changed. */ private final class PropertyChangeHandler implements PropertyChangeListener { /** * A bean property has been changed. Sets the changed state to true. * Checks whether the observed or multiple properties have changed. * If so, notifies all registered listeners about the change. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { setChanged(true); String propertyName = evt.getPropertyName(); if (propertyName == null) { forwardAllAdaptedValuesChanged(); } else { SimplePropertyAdapter adapter = getPropertyAdapter(propertyName); if (adapter != null) { adapter.fireValueChange(evt.getOldValue(), evt.getNewValue(), true); } } } } /** * Implements the access to the individual bean properties. * All SimplePropertyAdapters created by this BeanAdapter * share a single PropertyChangeListener that is used to * fire value changes in this SimplePropertyAdapter.

* * This class is public to enable reflection access. */ public class SimplePropertyAdapter extends AbstractValueModel { /** * Holds the name of the adapted property. */ private final String propertyName; /** * Holds the optional name of the property's getter. * Used to create the PropertyDescriptor. * Also used to reject potential misuse of * {@link BeanAdapter#getValueModel(String)} and * {@link BeanAdapter#getValueModel(String, String, String)}. * See the latter methods for details. */ final String getterName; /** * Holds the optional name of the property's setter. * Used to create the PropertyDescriptor. * Also used to reject potential misuse of * {@link BeanAdapter#getValueModel(String)} and * {@link BeanAdapter#getValueModel(String, String, String)}. * See the latter methods for details. */ final String setterName; /** * Describes the property accessor; basically a getter and setter. */ private PropertyDescriptor cachedPropertyDescriptor; /** * Holds the bean class associated with the cached property descriptor. */ private Class cachedBeanClass; // Instance Creation -------------------------------------------------- /** * Constructs a SimplePropertyAdapter for the given property name, * getter and setter name. * * @param propertyName the name of the property to adapt * @param getterName the name of the method that reads the value * @param setterName the name of the method that sets the value */ protected SimplePropertyAdapter(String propertyName, String getterName, String setterName) { this.propertyName = propertyName; this.getterName = getterName; this.setterName = setterName; // Eagerly check the existence of the property to adapt. B bean = getBean(); if (bean != null) { getPropertyDescriptor(bean); } } // Implementing ValueModel -------------------------------------------- /** * Returns the value of the adapted bean property, or null * if the bean is null. * * @return the value of the adapted bean property, * null if the bean is null */ public Object getValue() { B bean = getBean(); return bean == null ? null : getValue0(bean, getPropertyDescriptor(bean)); } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. If the bean setter * throws a PropertyVetoException, it is silently ignored. * This write operation is supported only for writable bean properties.

* * Notifies any registered value listener if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal. * * @param newValue the value to set * * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set */ public void setValue(Object newValue) { B bean = getBean(); if (bean == null) { return; } try { setValue0(bean, getPropertyDescriptor(bean), newValue); } catch (PropertyVetoException e) { // Silently ignore that someone vetoed against this change } } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. If the bean setter * throws a PropertyVetoExeption, this method throws the same exception. * This write operation is supported only for writable bean properties.

* * Notifies any registered value listener if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal. * * @param newValue the value to set * * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set * @throws PropertyVetoException if the invoked bean setter * throws a PropertyVetoException * * @since 1.1 */ public void setVetoableValue(Object newValue) throws PropertyVetoException { B bean = getBean(); if (bean == null) { return; } setValue0(bean, getPropertyDescriptor(bean), newValue); } // Accessing the Cached Property Descriptor -------------------------- /** * Looks up, lazily initializes and returns a PropertyDescriptor * for the given Java Bean and name of the adapted property.

* * The cached PropertyDescriptor is considered invalid if the * bean's class has changed. In this case we recompute the * PropertyDescriptor.

* * If a getter name or setter name is available, these are used * to directly create a PropertyDescriptor. Otherwise, the standard * Java Bean introspection is used to determine the property descriptor. * * @param bean the bean that holds the property * @return the PropertyDescriptor * @throws PropertyNotFoundException if the property could not be found */ private PropertyDescriptor getPropertyDescriptor(B bean) { Class beanClass = getBeanClass(bean); if (cachedPropertyDescriptor == null || beanClass != cachedBeanClass) { cachedPropertyDescriptor = BeanUtils.getPropertyDescriptor( beanClass, propertyName, getterName, setterName); cachedBeanClass = beanClass; } return cachedPropertyDescriptor; } protected void fireChange(B currentBean) { Object newValue; if (currentBean == null) { newValue = null; } else { PropertyDescriptor propertyDescriptor = getPropertyDescriptor(currentBean); boolean isWriteOnly = null == propertyDescriptor.getReadMethod(); newValue = isWriteOnly ? null : getValue0(currentBean, propertyDescriptor); } fireValueChange(null, newValue); } protected void setBean0(B oldBean, B newBean) { Object oldValue; Object newValue; if (oldBean == null) { oldValue = null; } else { PropertyDescriptor propertyDescriptor = getPropertyDescriptor(oldBean); boolean isWriteOnly = null == propertyDescriptor.getReadMethod(); oldValue = isWriteOnly ? null : getValue0(oldBean, propertyDescriptor); } if (newBean == null) { newValue = null; } else { PropertyDescriptor propertyDescriptor = getPropertyDescriptor(newBean); boolean isWriteOnly = null == propertyDescriptor.getReadMethod(); newValue = isWriteOnly ? null : getValue0(newBean, propertyDescriptor); } if (oldValue != null || newValue != null) { fireValueChange(oldValue, newValue, true); } } @Override protected String paramString() { B bean = getBean(); String beanType = null; Object value = getValue(); String valueType = null; String propertyDescriptorName = null; String propertyType = null; Method propertySetter = null; if (bean != null) { beanType = bean.getClass().getName(); valueType = value == null ? null : value.getClass().getName(); PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean); propertyDescriptorName = propertyDescriptor.getName(); propertyType = propertyDescriptor.getPropertyType().getName(); propertySetter = propertyDescriptor.getWriteMethod(); } return "bean=" + bean + "; bean type=" + beanType + "; value=" + value + "; value type=" + valueType + "; property name=" + propertyDescriptorName + "; property type=" + propertyType + "; property setter=" + propertySetter; } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/BeanUtils.java0000644000175000017500000006500411374522114026223 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Consists exclusively of static methods that provide * convenience behavior for working with Java Bean properties. * * @author Karsten Lentzsch * @version $Revision: 1.18 $ * * @see Introspector * @see BeanInfo * @see PropertyDescriptor */ public final class BeanUtils { private BeanUtils() { // Override default constructor; prevents instantiation. } /** * Checks and answers whether the given class supports bound properties, * i.e. it provides a pair of multicast event listener registration methods * for PropertyChangeListeners: *

     * public void addPropertyChangeListener(PropertyChangeListener x);
     * public void removePropertyChangeListener(PropertyChangeListener x);
     * 
* * @param clazz the class to test * @return true if the class supports bound properties, false otherwise */ public static boolean supportsBoundProperties(Class clazz) { return getPCLAdder(clazz) != null && getPCLRemover(clazz) != null; } /** * Looks up and returns a PropertyDescriptor for the * given Java Bean class and property name using the standard * Java Bean introspection behavior. * * @param beanClass the type of the bean that holds the property * @param propertyName the name of the Bean property * @return the PropertyDescriptor associated with the given * bean and property name as returned by the Bean introspection * * @throws IntrospectionException if an exception occurs during * introspection. * @throws NullPointerException if the beanClass or propertyName is {@code null} * * @since 1.1.1 */ public static PropertyDescriptor getPropertyDescriptor( Class beanClass, String propertyName) throws IntrospectionException { BeanInfo info = Introspector.getBeanInfo(beanClass); for (PropertyDescriptor element : info.getPropertyDescriptors()) { if (propertyName.equals(element.getName())) { return element; } } throw new IntrospectionException( "Property '" + propertyName + "' not found in bean " + beanClass); } /** * Looks up and returns a PropertyDescriptor for the given * Java Bean class and property name. If a getter name or setter name * is available, these are used to create a PropertyDescriptor. * Otherwise, the standard Java Bean introspection is used to determine * the property descriptor. * * @param beanClass the class of the bean that holds the property * @param propertyName the name of the property to be accessed * @param getterName the optional name of the property's getter * @param setterName the optional name of the property's setter * @return the PropertyDescriptor associated with the * given bean and property name * * @throws PropertyNotFoundException if the property could not be found * * @since 1.1.1 */ public static PropertyDescriptor getPropertyDescriptor( Class beanClass, String propertyName, String getterName, String setterName) { try { return getterName != null || setterName != null ? new PropertyDescriptor( propertyName, beanClass, getterName, setterName) : getPropertyDescriptor( beanClass, propertyName); } catch (IntrospectionException e) { throw new PropertyNotFoundException(propertyName, beanClass, e); } } /** * Holds the class parameter list that is used to lookup * the adder and remover methods for PropertyChangeListeners. */ private static final Class[] PCL_PARAMS = new Class[] {PropertyChangeListener.class}; /** * Holds the class parameter list that is used to lookup * the adder and remover methods for PropertyChangeListeners. */ private static final Class[] NAMED_PCL_PARAMS = new Class[] {String.class, PropertyChangeListener.class}; /** * Looks up and returns the method that adds a multicast * PropertyChangeListener to instances of the given class. * * @param clazz the class that provides the adder method * @return the method that adds multicast PropertyChangeListeners */ public static Method getPCLAdder(Class clazz) { try { return clazz.getMethod("addPropertyChangeListener", PCL_PARAMS); } catch (NoSuchMethodException e) { return null; } } /** * Looks up and returns the method that removes a multicast * PropertyChangeListener from instances of the given class. * * @param clazz the class that provides the remover method * @return the method that removes multicast PropertyChangeListeners */ public static Method getPCLRemover(Class clazz) { try { return clazz.getMethod("removePropertyChangeListener", PCL_PARAMS); } catch (NoSuchMethodException e) { return null; } } /** * Looks up and returns the method that adds a PropertyChangeListener * for a specified property name to instances of the given class. * * @param clazz the class that provides the adder method * @return the method that adds the PropertyChangeListeners */ public static Method getNamedPCLAdder(Class clazz) { try { return clazz.getMethod("addPropertyChangeListener", NAMED_PCL_PARAMS); } catch (NoSuchMethodException e) { return null; } } /** * Looks up and returns the method that removes a PropertyChangeListener * for a specified property name from instances of the given class. * * @param clazz the class that provides the remover method * @return the method that removes the PropertyChangeListeners */ public static Method getNamedPCLRemover(Class clazz) { try { return clazz.getMethod("removePropertyChangeListener", NAMED_PCL_PARAMS); } catch (NoSuchMethodException e) { return null; } } /** * Adds a property change listener to the given bean. First checks * whether the bean supports bound properties, i.e. it provides * a pair of methods to register multicast property change event listeners; * see section 7.4.1 of the Java Beans specification for details. * * @param bean the bean to add the property change listener to * @param beanClass the Bean class used to lookup methods from * @param listener the listener to add * * @throws NullPointerException * if the bean or listener is {@code null} * @throws IllegalArgumentException * if the bean is not an instance of the bean class * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be added successfully * * @since 1.1.1 */ public static void addPropertyChangeListener(Object bean, Class beanClass, PropertyChangeListener listener) { checkNotNull(listener, "The listener must not be null."); if (beanClass == null) { beanClass = bean.getClass(); } else { checkArgument(beanClass.isInstance(bean), "The bean " + bean + " must be an instance of " + beanClass); } if (bean instanceof Model) { ((Model) bean).addPropertyChangeListener(listener); return; } // Check whether the bean supports bound properties. if (!BeanUtils.supportsBoundProperties(beanClass)) { throw new PropertyUnboundException( "Bound properties unsupported by bean class=" + beanClass + "\nThe Bean class must provide a pair of methods:" + "\npublic void addPropertyChangeListener(PropertyChangeListener x);" + "\npublic void removePropertyChangeListener(PropertyChangeListener x);"); } Method multicastPCLAdder = getPCLAdder(beanClass); try { multicastPCLAdder.invoke(bean, listener); } catch (InvocationTargetException e) { throw new PropertyNotBindableException( "Due to an InvocationTargetException we failed to add " + "a multicast PropertyChangeListener to bean: " + bean, e.getCause()); } catch (IllegalAccessException e) { throw new PropertyNotBindableException( "Due to an IllegalAccessException we failed to add " + "a multicast PropertyChangeListener to bean: " + bean, e); } } /** * Adds a named property change listener to the given bean. The bean * must provide the optional support for listening on named properties * as described in section 7.4.5 of the * Java Bean * Specification. The bean class must provide the method: *
     * public void addPropertyChangeListener(String name, PropertyChangeListener l);
     * 
* * @param bean the bean to add a property change handler * @param beanClass the Bean class used to lookup methods from * @param propertyName the name of the property to be observed * @param listener the listener to add * * @throws NullPointerException * if the bean, propertyName or listener is {@code null} * @throws IllegalArgumentException * if the bean is not an instance of the bean class * @throws PropertyNotBindableException * if the property change handler cannot be added successfully */ public static void addPropertyChangeListener( Object bean, Class beanClass, String propertyName, PropertyChangeListener listener) { checkNotNull(propertyName, "The property name must not be null."); checkNotNull(listener, "The listener must not be null."); if (beanClass == null) { beanClass = bean.getClass(); } else { checkArgument(beanClass.isInstance(bean), "The bean " + bean + " must be an instance of " + beanClass); } if (bean instanceof Model) { ((Model) bean).addPropertyChangeListener(propertyName, listener); return; } Method namedPCLAdder = getNamedPCLAdder(beanClass); if (namedPCLAdder == null) { throw new PropertyNotBindableException( "Could not find the bean method" + "\npublic void addPropertyChangeListener(String, PropertyChangeListener);" + "\nin bean: " + bean); } try { namedPCLAdder.invoke(bean, propertyName, listener); } catch (InvocationTargetException e) { throw new PropertyNotBindableException( "Due to an InvocationTargetException we failed to add " + "a named PropertyChangeListener to bean: " + bean, e.getCause()); } catch (IllegalAccessException e) { throw new PropertyNotBindableException( "Due to an IllegalAccessException we failed to add " + "a named PropertyChangeListener to bean: " + bean, e); } } /** * Adds a property change listener to the given bean. First checks * whether the bean supports bound properties, i.e. it provides * a pair of methods to register multicast property change event listeners; * see section 7.4.1 of the Java Beans specification for details. * * @param bean the bean to add the property change listener to * @param listener the listener to add * * @throws NullPointerException * if the bean or listener is {@code null} * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be added successfully */ public static void addPropertyChangeListener(Object bean, PropertyChangeListener listener) { addPropertyChangeListener(bean, bean.getClass(), listener); } /** * Adds a named property change listener to the given bean. The bean * must provide the optional support for listening on named properties * as described in section 7.4.5 of the * Java Bean * Specification. The bean class must provide the method: *
     * public void addPropertyChangeListener(String name, PropertyChangeListener l);
     * 
* * @param bean the bean to add a property change handler * @param propertyName the name of the property to be observed * @param listener the listener to add * * @throws NullPointerException * if the bean, propertyName or listener is {@code null} * @throws PropertyNotBindableException * if the property change handler cannot be added successfully */ public static void addPropertyChangeListener( Object bean, String propertyName, PropertyChangeListener listener) { addPropertyChangeListener(bean, bean.getClass(), propertyName, listener); } /** * Removes a property change listener from the given bean. * * @param bean the bean to remove the property change listener from * @param beanClass the Java Bean class used to lookup methods from * @param listener the listener to remove * * @throws NullPointerException * if the bean or listener is {@code null} * @throws IllegalArgumentException * if the bean is not an instance of the bean class * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully * * @since 1.1.1 */ public static void removePropertyChangeListener(Object bean, Class beanClass, PropertyChangeListener listener) { checkNotNull(listener, "The listener must not be null."); if (beanClass == null) { beanClass = bean.getClass(); } else { checkArgument(beanClass.isInstance(bean), "The bean " + bean + " must be an instance of " + beanClass); } if (bean instanceof Model) { ((Model) bean).removePropertyChangeListener(listener); return; } Method multicastPCLRemover = getPCLRemover(beanClass); if (multicastPCLRemover == null) { throw new PropertyUnboundException( "Could not find the method:" + "\npublic void removePropertyChangeListener(String, PropertyChangeListener x);" + "\nfor bean:" + bean); } try { multicastPCLRemover.invoke(bean, listener); } catch (InvocationTargetException e) { throw new PropertyNotBindableException( "Due to an InvocationTargetException we failed to remove " + "a multicast PropertyChangeListener from bean: " + bean, e.getCause()); } catch (IllegalAccessException e) { throw new PropertyNotBindableException( "Due to an IllegalAccessException we failed to remove " + "a multicast PropertyChangeListener from bean: " + bean, e); } } /** * Removes a named property change listener from the given bean. The bean * must provide the optional support for listening on named properties * as described in section 7.4.5 of the * Java Bean * Specification. The bean class must provide the method: *
     * public void removePropertyChangeHandler(String name, PropertyChangeListener l);
     * 
* * @param bean the bean to remove the property change listener from * @param beanClass the Java Bean class used to lookup methods from * @param propertyName the name of the observed property * @param listener the listener to remove * * @throws NullPointerException * if the bean, propertyName, or listener is {@code null} * @throws IllegalArgumentException * if the bean is not an instance of the bean class * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully * * @since 1.1.1 */ public static void removePropertyChangeListener( Object bean, Class beanClass, String propertyName, PropertyChangeListener listener) { checkNotNull(propertyName, "The property name must not be null."); checkNotNull(listener, "The listener must not be null."); if (beanClass == null) { beanClass = bean.getClass(); } else { checkArgument(beanClass.isInstance(bean), "The bean " + bean + " must be an instance of " + beanClass); } if (bean instanceof Model) { ((Model) bean).removePropertyChangeListener(propertyName, listener); return; } Method namedPCLRemover = getNamedPCLRemover(beanClass); if (namedPCLRemover == null) { throw new PropertyNotBindableException( "Could not find the bean method" + "\npublic void removePropertyChangeListener(String, PropertyChangeListener);" + "\nin bean: " + bean); } try { namedPCLRemover.invoke(bean, propertyName, listener); } catch (InvocationTargetException e) { throw new PropertyNotBindableException( "Due to an InvocationTargetException we failed to remove " + "a named PropertyChangeListener from bean: " + bean, e.getCause()); } catch (IllegalAccessException e) { throw new PropertyNotBindableException( "Due to an IllegalAccessException we failed to remove " + "a named PropertyChangeListener from bean: " + bean, e); } } /** * Removes a property change listener from the given bean. * * @param bean the bean to remove the property change listener from * @param listener the listener to remove * @throws NullPointerException if the bean or listener is {@code null} * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully */ public static void removePropertyChangeListener(Object bean, PropertyChangeListener listener) { removePropertyChangeListener(bean, bean.getClass(), listener); } /** * Removes a named property change listener from the given bean. The bean * must provide the optional support for listening on named properties * as described in section 7.4.5 of the * Java Bean * Specification. The bean class must provide the method: *
     * public void removePropertyChangeHandler(String name, PropertyChangeListener l);
     * 
* * @param bean the bean to remove the property change listener from * @param propertyName the name of the observed property * @param listener the listener to remove * @throws NullPointerException * if the bean, propertyName, or listener is {@code null} * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully */ public static void removePropertyChangeListener( Object bean, String propertyName, PropertyChangeListener listener) { removePropertyChangeListener(bean, bean.getClass(), propertyName, listener); } // Getting and Setting Property Values ************************************ /** * Returns the value of the specified property of the given non-null bean. * This operation is unsupported if the bean property is read-only.

* * If the read access fails, a PropertyAccessException is thrown * that provides the Throwable that caused the failure. * * @param bean the bean to read the value from * @param propertyDescriptor describes the property to be read * @return the bean's property value * * @throws NullPointerException if the bean is {@code null} * @throws UnsupportedOperationException if the bean property is write-only * @throws PropertyAccessException if the new value could not be read */ public static Object getValue(Object bean, PropertyDescriptor propertyDescriptor) { checkNotNull(bean, "The bean must not be null."); Method getter = propertyDescriptor.getReadMethod(); if (getter == null) { throw new UnsupportedOperationException( "The property '" + propertyDescriptor.getName() + "' is write-only."); } try { return getter.invoke(bean, (Object[]) null); } catch (InvocationTargetException e) { throw PropertyAccessException.createReadAccessException( bean, propertyDescriptor, e.getCause()); } catch (IllegalAccessException e) { throw PropertyAccessException.createReadAccessException( bean, propertyDescriptor, e); } } /** * Sets the given object as new value of the specified property of the given * non-null bean. This is unsupported if the bean property is read-only.

* * If the write access fails, a PropertyAccessException is thrown * that provides the Throwable that caused the failure. * If the bean property is constrained and a VetoableChangeListener * has vetoed against the value change, the PropertyAccessException * wraps the PropertyVetoException thrown by the setter. * * @param bean the bean that holds the adapted property * @param propertyDescriptor describes the property to be set * @param newValue the property value to be set * * @throws NullPointerException if the bean is {@code null} * @throws UnsupportedOperationException if the bean property is read-only * @throws PropertyAccessException if the new value could not be set * @throws PropertyVetoException if the bean setter throws this exception */ public static void setValue(Object bean, PropertyDescriptor propertyDescriptor, Object newValue) throws PropertyVetoException { checkNotNull(bean, "The bean must not be null."); Method setter = propertyDescriptor.getWriteMethod(); if (setter == null) { throw new UnsupportedOperationException( "The property '" + propertyDescriptor.getName() + "' is read-only."); } try { setter.invoke(bean, newValue); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof PropertyVetoException) { throw (PropertyVetoException) cause; } throw PropertyAccessException.createWriteAccessException( bean, newValue, propertyDescriptor, cause); } catch (IllegalAccessException e) { throw PropertyAccessException.createWriteAccessException( bean, newValue, propertyDescriptor, e); } catch (IllegalArgumentException e) { throw PropertyAccessException.createWriteAccessException( bean, newValue, propertyDescriptor, e); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/PropertyUnboundException.java0000644000175000017500000000643411374522114031375 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; /** * A runtime exception that describes that a Java Bean does not * support bound properties. * The conditions for bound properties are specified in section 7.4 of the * Java * Bean Specification. Basically you must provide the following two methods: *

 * public void addPropertyChangeListener(PropertyChangeListener x);
 * public void removePropertyChangeListener(PropertyChangeListener x);
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.9 $ * * @see com.jgoodies.binding.beans.BeanAdapter * @see PropertyAdapter */ public final class PropertyUnboundException extends PropertyException { /** * Constructs a new exception instance with the specified detail message. * The cause is not initialized. * * @param message the detail message which is saved for later retrieval by * the {@link #getMessage()} method. */ public PropertyUnboundException(String message) { super(message); } /** * Constructs a new exception instance with the specified detail message * and cause. * * @param message the detail message which is saved for later retrieval by * the {@link #getMessage()} method. * @param cause the cause which is saved for later retrieval by the * {@link #getCause()} method. A {@code null} value is permitted, * and indicates that the cause is nonexistent or unknown. */ public PropertyUnboundException(String message, Throwable cause) { super(message, cause); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/beans/PropertyAdapter.java0000644000175000017500000012277511374522114027473 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.beans; import static com.jgoodies.common.base.Preconditions.checkNotBlank; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyDescriptor; import java.beans.PropertyVetoException; import java.lang.reflect.Method; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * Converts a single Java Bean property into the generic ValueModel interface. * The bean property must be a single value as described by the * Java * Bean Specification. See below for a comparison with the more frequently * used BeanAdapter and PresentationModel classes.

* * The constructors accept either a property name or a triple of * (property name, getter name, setter name). If you just specify the * property name, the adapter uses the standard Java Bean introspection * to lookup the available properties and how to read and write the * property value. In case of custom readers and writers you may * specify a custom BeanInfo class, or as a shortcut use the constructors * that accept the optional getter and setter name. If these are specified, * introspection will be bypassed and a PropertyDescriptor will be * created for the given property name, getter and setter name.

* * Optionally the PropertyAdapter can observe changes in bound * properties as described in section 7.4 of the Bean specification. * You can enable this feature by setting the constructor parameter * observeChanges to {@code true}. * If the adapter observes changes, it will fire value change events, * i.e. PropertyChangeEvents for the property "value". * Even if you ignore property changes, you can access the adapted * property value via #getValue(). * It's just that you won't be notified about changes.

* * The PropertyAdapter provides two access styles to the target bean * that holds the adapted property: you can specify a bean directly, * or you can use a bean channel to access the bean indirectly. * In the latter case you specify a ValueModel * that holds the bean that in turn holds the adapted property.

* * If the adapted bean is {@code null} the PropertyAdapter can * neither read nor set a value. In this case #getValue * returns {@code null} and #setValue will silently * ignore the new value.

* * This adapter throws three PropertyChangeEvents if the bean changes: * beforeBean, bean and afterBean. This is useful * when sharing a bean channel and you must perform an operation before * or after other listeners handle a bean change. Since you cannot rely * on the order listeners will be notified, only the beforeBean * and afterBean events are guaranteed to be fired before and * after the bean change is fired. * Note that #getBean() returns the new bean before * any of these three PropertyChangeEvents is fired. Therefore listeners * that handle these events must use the event's old and new value * to determine the old and new bean. * The order of events fired during a bean change is:

    *
  1. this adapter's bean channel fires a value change, *
  2. this adapter fires a beforeBean change, *
  3. this adapter fires the bean change, *
  4. this adapter fires an afterBean change. *

* * Note: * PropertyAdapters that observe changes have a PropertyChangeListener * registered with the target bean. Hence, a bean has a reference * to any PropertyAdapter that observes it. To avoid memory leaks * it is recommended to remove this listener if the bean lives much longer than * the PropertyAdapter, enabling the garbage collector to remove the adapter. * To do so, you can call setBean(null) or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with WeakReference.

* * Setting the bean to null has side-effects, for example the adapter fires * a change event for the bound property bean and other properties. * And the adpter's value may change. * However, typically this is fine and setting the bean to null * is the first choice for removing the reference from the bean to the adapter. * Another way to clear the reference from the target bean is * to call #release. It has no side-effects, but the adapter * must not be used anymore once #release has been called.

* * Constraints: If property changes shall be observed, * the bean class must support bound properties, i. e. it must provide * the following pair of methods for registration of multicast property * change event listeners: *

 * public void addPropertyChangeListener(PropertyChangeListener x);
 * public void removePropertyChangeListener(PropertyChangeListener x);
 * 
* * PropertyAdapter vs. BeanAdapter vs. PresentationModel
* If you adapt multiple properties of the same bean, you better use * a {@link com.jgoodies.binding.beans.BeanAdapter}. The BeanAdapter * registers only a single PropertyChangeListener with the bean, * where multiple PropertyAdapters would register multiple listeners. * If you adapt bean properties for an editor, you will typically use the * {@link com.jgoodies.binding.PresentationModel}. The PresentationModel is * more powerful than the BeanAdapter. It adds support for buffered models, * and provides an extensible mechanism for observing the change state * of the bean and related objects.

* * Basic Examples: *

 * // Direct access, ignores changes
 * Address address = new Address()
 * PropertyAdapter adapter = new PropertyAdapter(address, "street");
 * adapter.setValue("Broadway");
 * System.out.println(address.getStreet());    // Prints "Broadway"
 * address.setStreet("Franz-Josef-Strasse");
 * System.out.println(adapter.getValue());     // Prints "Franz-Josef-Strasse"
 *
 *
 * //Direct access, observes changes
 * PropertyAdapter adapter = new PropertyAdapter(address, "street", true);
 *
 *
 * // Indirect access, ignores changes
 * ValueHolder addressHolder = new ValueHolder(address1);
 * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street");
 * adapter.setValue("Broadway");               // Sets the street in address1
 * System.out.println(address1.getValue());    // Prints "Broadway"
 * adapter.setBean(address2);
 * adapter.setValue("Robert-Koch-Strasse");    // Sets the street in address2
 * System.out.println(address2.getValue());    // Prints "Robert-Koch-Strasse"
 *
 *
 * // Indirect access, observes changes
 * ValueHolder addressHolder = new ValueHolder();
 * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street", true);
 * addressHolder.setValue(address1);
 * address1.setStreet("Broadway");
 * System.out.println(adapter.getValue());     // Prints "Broadway"
 * 
* * Adapter Chain Example: *
Builds an adapter chain from a domain model to the presentation layer. *
 * Country country = new Country();
 * country.setName("Germany");
 * country.setEuMember(true);
 *
 * JTextField nameField = new JTextField();
 * nameField.setDocument(new DocumentAdapter(
 *      new PropertyAdapter(country, "name", true)));
 *
 * JCheckBox euMemberBox = new JCheckBox("Is EU Member");
 * euMemberBox.setModel(new ToggleButtonAdapter(
 *      new PropertyAdapter(country, "euMember", true)));
 *
 * // Using factory methods
 * JTextField nameField   = Factory.createTextField(country, "name");
 * JCheckBox  euMemberBox = Factory.createCheckBox (country, "euMember");
 * euMemberBox.setText("Is EU Member");
 * 

* * TODO: Consider adding a feature to ensure that update notifications * are performed in the event dispatch thread. In case the adapted bean * is changed in a thread other than the event dispatch thread, such * a feature would help complying with Swing's single thread rule. * The feature could be implemented by an extended PropertyChangeSupport.

* * TODO: I plan to improve the support for adapting beans that do not fire * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter, * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's * internal SimplePropertyAdapter's shall be able to optionally self-fire * a PropertyChangeEvent in case the bean does not. There are several * downsides with self-firing events compared to bound bean properties. * See Issue * 49 for more information about the downsides.

* * The observeChanges constructor parameter shall be replaced by a more * fine-grained choice to not observe (former observeChanges=false), * to observe bound properties (former observeChanges=true), and a new * setting for self-firing PropertyChangeEvents if a value is set. * The latter case may be further splitted up to specify how the * self-fired PropertyChangeEvent is created: *

    *
  1. oldValue=null, newValue=null *
  2. oldValue=null, newValue=the value set *
  3. oldValue=value read before the set, newValue=the value set *
  4. oldValue=value read before the set, newValue=value read after the set *
* * @author Karsten Lentzsch * @version $Revision: 1.21 $ * * @see com.jgoodies.binding.beans.BeanAdapter * @see ValueModel * @see ValueModel#getValue() * @see ValueModel#setValue(Object) * @see PropertyChangeEvent * @see PropertyChangeListener * @see java.beans.Introspector * @see java.beans.BeanInfo * @see PropertyDescriptor * * @param the type of the adapted bean */ public final class PropertyAdapter extends AbstractValueModel { /** * The property name used in the PropertyChangeEvent that is fired * before the bean property fires its PropertyChangeEvent. * Useful to perform an operation before listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean"; /** * The name of the read-write bound property that holds the target bean. * * @see #getBean() * @see #setBean(Object) */ public static final String PROPERTYNAME_BEAN = "bean"; /** * The property name used in the PropertyChangeEvent that is fired * after the bean property fires its PropertyChangeEvent. * Useful to perform an operation after listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTYNAME_AFTER_BEAN = "afterBean"; /** * The name of the read-only bound bean property that * indicates whether one of the observed properties has changed. * * @see #isChanged() */ public static final String PROPERTYNAME_CHANGED = "changed"; // Fields ***************************************************************** /** * Holds a ValueModel that holds the bean, that in turn * holds the adapted property. * * @see #getBean() * @see #setBean(Object) */ private final ValueModel beanChannel; /** * Holds the name of the adapted property. * * @see #getPropertyName() */ private final String propertyName; /** * Holds the optional name of the property's getter. */ private final String getterName; /** * Holds the optional name of the property's setter. */ private final String setterName; /** * Specifies whether we observe property changes and in turn * fire state changes. * * @see #getObserveChanges() */ private final boolean observeChanges; /** * Refers to the old bean. Used as old value if the bean changes. * Updated after a bean change in the BeanChangeHandler. */ private B storedOldBean; /** * Indicates whether a property in the current target been has changed. * Will be reset to {@code false} every time the target bean changes. * * @see #isChanged() * @see #setBean(Object) */ private boolean changed = false; /** * The PropertyChangeListener used to handle changes * in the adapted bean property. A new instance is created every time * the target bean changes. */ private PropertyChangeListener propertyChangeHandler; /** * Describes the property accessor; basically a getter and setter. */ private PropertyDescriptor cachedPropertyDescriptor; /** * Holds the bean class associated with the cached property descriptor. */ private Class cachedBeanClass; // Instance creation **************************************************** /** * Constructs a PropertyAdapter for the given * bean and property name; does not observe changes. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @throws NullPointerException if propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty */ public PropertyAdapter(B bean, String propertyName) { this(bean, propertyName, false); } /** * Constructs a PropertyAdapter for the given * bean and property name; observes changes if specified. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * @throws NullPointerException if propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty * @throws PropertyUnboundException if observeChanges * is true but the property is unbound, i. e. the bean * does not provide a pair of methods to register a multicast * PropertyChangeListener */ public PropertyAdapter( B bean, String propertyName, boolean observeChanges) { this(bean, propertyName, null, null, observeChanges); } /** * Constructs a PropertyAdapter for the given bean, * property name, getter and setter name; does not observe changes. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @throws NullPointerException if propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty */ public PropertyAdapter(B bean, String propertyName, String getterName, String setterName) { this(bean, propertyName, getterName, setterName, false); } /** * Constructs a PropertyAdapter for the given bean, * property name, getter and setter name; observes changes if specified. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * @throws NullPointerException if propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty * @throws PropertyUnboundException if observeChanges * is true but the property is unbound, i. e. the bean * does not provide a pair of methods to register a multicast * PropertyChangeListener */ public PropertyAdapter( B bean, String propertyName, String getterName, String setterName, boolean observeChanges) { this(new ValueHolder(bean, true), propertyName, getterName, setterName, observeChanges); } /** * Constructs a PropertyAdapter for the given * bean channel and property name; does not observe changes. * * @param beanChannel the ValueModel that holds the bean * @param propertyName the name of the adapted property * @throws NullPointerException if beanChannel or * propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty */ public PropertyAdapter(ValueModel beanChannel, String propertyName) { this(beanChannel, propertyName, false); } /** * Constructs a PropertyAdapter for the given * bean channel and property name; observes changes if specified. * * @param beanChannel the ValueModel that holds the bean * @param propertyName the name of the adapted property * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * @throws NullPointerException if beanChannel or * propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty * @throws PropertyUnboundException if observeChanges * is true but the property is unbound, i. e. the bean * does not provide a pair of methods to register a multicast * PropertyChangeListener * @throws PropertyAccessException if the beanChannel's value * does not provide a property descriptor for propertyName */ public PropertyAdapter( ValueModel beanChannel, String propertyName, boolean observeChanges) { this(beanChannel, propertyName, null, null, observeChanges); } /** * Constructs a PropertyAdapter for the given bean channel, * property name, getter and setter name; does not observe changes. * * @param beanChannel the ValueModel that holds the bean * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @throws NullPointerException if beanChannel or * propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty */ public PropertyAdapter(ValueModel beanChannel, String propertyName, String getterName, String setterName) { this(beanChannel, propertyName, getterName, setterName, false); } /** * Constructs a PropertyAdapter for the given bean channel, * property name, getter and setter name; observes changes if specified. * * @param beanChannel the ValueModel that holds the bean * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * * @throws NullPointerException if propertyName is {@code null} * @throws IllegalArgumentException if propertyName is empty * @throws IllegalArgumentException if the bean channel is a ValueHolder * that has the identityCheck feature disabled * @throws PropertyUnboundException if observeChanges * is true but the property is unbound, i. e. the bean * does not provide a pair of methods to register a multicast * PropertyChangeListener * @throws PropertyAccessException if the beanChannel's value * does not provide a property descriptor for propertyName */ public PropertyAdapter( ValueModel beanChannel, String propertyName, String getterName, String setterName, boolean observeChanges) { this.beanChannel = beanChannel != null ? beanChannel : new ValueHolder(null, true); this.propertyName = propertyName; this.getterName = getterName; this.setterName = setterName; this.observeChanges = observeChanges; checkNotBlank(propertyName, "The property name must not be null, empty, or whitespace."); checkBeanChannelIdentityCheck(beanChannel); this.beanChannel.addValueChangeListener(new BeanChangeHandler()); B initialBean = getBean(); // Eagerly check the existence of the property to adapt. if (initialBean != null) { getPropertyDescriptor(initialBean); addChangeHandlerTo(initialBean); } storedOldBean = initialBean; } // Accessors ************************************************************ /** * Returns the Java Bean that holds the adapted property. * * @return the Bean that holds the adapted property * * @see #setBean(Object) */ public B getBean() { return (B) beanChannel.getValue(); } /** * Sets a new Java Bean as holder of the adapted property. * Notifies any registered value listeners if the value has changed. * Also notifies listeners that have been registered with this adapter * to observe the bound property bean. * * @param newBean the new holder of the property * * @see #getBean() */ public void setBean(B newBean) { beanChannel.setValue(newBean); } /** * Returns the name of the adapted Java Bean property. * * @return the name of the adapted property */ public String getPropertyName() { return propertyName; } /** * Answers whether this adapter observes changes in the * adapted Bean property. * * @return true if this adapter observes changes, false if not */ public boolean getObserveChanges() { return observeChanges; } /* * Sets whether changes in the adapted Bean property shall be observed. * As a requirement the property must be bound. * * @param newValue true to observe changes, false to ignore them public void setObserveChanges(boolean newValue) { if (newValue == getObserveChanges()) return; observeChanges = newValue; Object bean = getBean(); removePropertyChangeHandler(bean); addPropertyChangeHandler(bean); } */ // ValueModel Implementation ******************************************** /** * Returns the value of the bean's adapted property, {@code null} * if the current bean is {@code null}.

* * If the adapted bean property is write-only, this adapter is write-only * too, and this operation is not supported and throws an exception. * * @return the value of the adapted bean property, null if the bean is null * @throws UnsupportedOperationException if the property is write-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the value could not be read */ public Object getValue() { B bean = getBean(); if (bean == null) { return null; } return getValue0(bean); } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. If the bean setter * throws a PropertyVetoException, it is silently ignored. * This write operation is supported only for writable bean properties.

* * Notifies any registered value listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal. * * @param newValue the value to set * * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set */ public void setValue(Object newValue) { B bean = getBean(); if (bean == null) { return; } try { setValue0(bean, newValue); } catch (PropertyVetoException e) { // Silently ignore this situation. } } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. If the bean setter * throws a PropertyVetoExeption, this method throws the same exception. * This write operation is supported only for writable bean properties.

* * Notifies any registered value listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal. * * @param newValue the value to set * * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set * @throws PropertyVetoException if the invoked bean setter * throws a PropertyVetoException * * @since 1.1 */ public void setVetoableValue(Object newValue) throws PropertyVetoException { B bean = getBean(); if (bean == null) { return; } setValue0(getBean(), newValue); } // Accessing the Changed State ******************************************** /** * Answers whether a bean property has changed since the changed state * has been reset. The changed state is implicitly reset every time * the target bean changes. * * @return true if a property of the current target bean * has changed since the last reset */ public boolean isChanged() { return changed; } /** * Resets this tracker's changed state to {@code false}. */ public void resetChanged() { setChanged(false); } /** * Sets the changed state to the given value. Invoked by the global * PropertyChangeHandler that observes all bean changes. Also invoked * by #resetChanged. * * @param newValue the new changed state */ private void setChanged(boolean newValue) { boolean oldValue = isChanged(); changed = newValue; firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue); } // Releasing PropertyChangeListeners ************************************** /** * Removes the PropertyChangeHandler from the observed bean, if the bean * is not {@code null} and if property changes are observed.

* * PropertyAdapters that observe changes have a PropertyChangeListener * registered with the target bean. Hence, a bean has a reference to all * PropertyAdapters that observe it. To avoid memory leaks it is recommended * to remove this listener if the bean lives much longer than the * PropertyAdapter, enabling the garbage collector to remove the adapter. * To do so, you can call setBean(null) or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with WeakReference.

* * Setting the bean to null has side-effects, for example * this adapter fires a change event for the bound property bean * and other properties. And this adpter's value may change. * However, typically this is fine and setting the bean to null is * the first choice for removing the reference from the bean to the adapter. * Another way to clear the reference from the target bean is * to call #release. It has no side-effects, but the adapter * must not be used anymore once #release has been called. * * @see #setBean(Object) * @see java.lang.ref.WeakReference */ public void release() { removeChangeHandlerFrom(getBean()); } // Overriding Superclass Behavior ***************************************** @Override protected String paramString() { B bean = getBean(); String beanType = null; Object value = getValue(); String valueType = null; String propertyDescriptorName = null; String propertyType = null; Method propertySetter = null; if (bean != null) { beanType = bean.getClass().getName(); valueType = value == null ? null : value.getClass().getName(); PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean); propertyDescriptorName = propertyDescriptor.getName(); propertyType = propertyDescriptor.getPropertyType().getName(); propertySetter = propertyDescriptor.getWriteMethod(); } return "bean=" + bean + "; bean type=" + beanType + "; value=" + value + "; value type=" + valueType + "; property name=" + propertyDescriptorName + "; property type=" + propertyType + "; property setter=" + propertySetter; } // Changing the Bean & Adding and Removing the PropertyChangeHandler ****** private void setBean0(B oldBean, B newBean) { firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean, true); removeChangeHandlerFrom(oldBean); forwardAdaptedValueChanged(oldBean, newBean); resetChanged(); addChangeHandlerTo(newBean); firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean, true); firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean, true); } private void forwardAdaptedValueChanged(B oldBean, B newBean) { Object oldValue = oldBean == null || isWriteOnlyProperty(oldBean) ? null : getValue0(oldBean); Object newValue = newBean == null || isWriteOnlyProperty(newBean) ? null : getValue0(newBean); if (oldValue != null || newValue != null) { fireValueChange(oldValue, newValue, true); } } private void forwardAdaptedValueChanged(B newBean) { Object newValue = newBean == null || isWriteOnlyProperty(newBean) ? null : getValue0(newBean); fireValueChange(null, newValue); } /** * Adds a property change listener to the given bean if we observe changes * and the bean is not null. First checks whether the bean class * supports bound properties, i.e. it provides a pair of methods * to register multicast property change event listeners; * see section 7.4.1 of the Java Beans specification for details. * * @param bean the bean to add a property change handler. * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be added successfully * * @see #removeChangeHandlerFrom(Object) */ private void addChangeHandlerTo(B bean) { if (!observeChanges || bean == null) { return; } propertyChangeHandler = new PropertyChangeHandler(); BeanUtils.addPropertyChangeListener(bean, getBeanClass(bean), propertyChangeHandler); } /** * Removes the formerly added property change handler from the given bean * if we observe changes and the bean is not null and we haven't called * this method before. * * @param bean the bean to remove the property change handler from. * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully * * @see #addChangeHandlerTo(Object) */ private void removeChangeHandlerFrom(B bean) { if (!observeChanges || bean == null || propertyChangeHandler == null) { return; } BeanUtils.removePropertyChangeListener(bean, getBeanClass(bean), propertyChangeHandler); propertyChangeHandler = null; } // Helper Methods to Get and Set a Property Value ************************* /** * Returns the Java Bean class used by this adapter. * The current implementation just returns the given bean's class.

* * A future version may return a type other than the concrete * class of the given bean. This beanClass could be specified * in a new set of constructors. This is useful if the beans * are specified by public interfaces, and implemented by * package private classes. In this case, the class of the given bean * object shall be checked against the specified type. * * @param bean the bean that may be used to lookup the class from * @return the Java Bean class used for this adapter. */ private Class getBeanClass(B bean) { return bean.getClass(); // The future version shall add a check like // beanClass.isInstance(bean) if the beanClass // has been specified in the constructor. } /** * Returns the current value of the bean's property, {@code null} * if the current bean is {@code null}. * * @param bean the bean to read the value from * @return the bean's property value */ private Object getValue0(B bean) { return bean == null ? null : BeanUtils.getValue(bean, getPropertyDescriptor(bean)); } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. This operation * is unsupported if the bean property is read-only.

* * The operation is delegated to the BeanUtils class. * * @param bean the bean that holds the adapted property * @param newValue the property value to be set * * @throws NullPointerException if the bean is null * @throws PropertyVetoException if the invoked bean setter * throws a PropertyVetoException */ private void setValue0(B bean, Object newValue) throws PropertyVetoException { BeanUtils.setValue(bean, getPropertyDescriptor(bean), newValue); } /** * Looks up, lazily initializes and returns a PropertyDescriptor * for the given Java Bean and name of the adapted property.

* * The cached PropertyDescriptor is considered invalid if the * bean's class has changed. In this case we recompute the * PropertyDescriptor.

* * If a getter name or setter name is available, these are used * to directly create a PropertyDescriptor. Otherwise, the standard * Java Bean introspection is used to determine the property descriptor. * * @param bean the bean that holds the property * @return the PropertyDescriptor * @throws PropertyNotFoundException if the property could not be found */ private PropertyDescriptor getPropertyDescriptor(B bean) { Class beanClass = getBeanClass(bean); if (cachedPropertyDescriptor == null || beanClass != cachedBeanClass) { cachedPropertyDescriptor = BeanUtils.getPropertyDescriptor( beanClass, getPropertyName(), getterName, setterName); cachedBeanClass = beanClass; } return cachedPropertyDescriptor; } /** * Answers whether the adapted property has a setter but no getter. * In this case the PropertyAdapter doesn't check for the old value * when you set a new bean or a new value. * * @param bean the bean to test for the write only state * @return true if the property has a setter but no getter, false otherwise */ private boolean isWriteOnlyProperty(B bean) { return null == getPropertyDescriptor(bean).getReadMethod(); } /** * Throws an IllegalArgumentException if the given ValueModel * is a ValueHolder that has the identityCheck feature disabled. */ private void checkBeanChannelIdentityCheck(ValueModel valueModel) { if (!(valueModel instanceof ValueHolder)) { return; } ValueHolder valueHolder = (ValueHolder) valueModel; if (!valueHolder.isIdentityCheckEnabled()) { throw new IllegalArgumentException( "The bean channel must have the identity check enabled."); } } // Helper Classes ********************************************************* /** * Listens to changes of the bean. */ private final class BeanChangeHandler implements PropertyChangeListener { /** * The bean has been changed. Uses the stored old bean instead of * the event's old value, because the latter can be null. * If the event's new value is null, the new bean is requested * from the bean channel. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { B newBean = evt.getNewValue() != null ? (B) evt.getNewValue() : getBean(); setBean0(storedOldBean, newBean); storedOldBean = newBean; } } /** * Listens to changes of all bean properties. Fires property changes * if the associated property or an arbitrary set of properties has changed. */ private final class PropertyChangeHandler implements PropertyChangeListener { /** * A bean property has been changed. Sets the changed state to true. * Checks whether the observed * property or multiple properties have changed. * If so, notifies all registered listeners about the change. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { setChanged(true); if (evt.getPropertyName() == null) { forwardAdaptedValueChanged(getBean()); } else if (evt.getPropertyName().equals(getPropertyName())) { fireValueChange(evt.getOldValue(), evt.getNewValue(), true); } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/util/0000755000175000017500000000000011374522114023352 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/binding/util/LoggingUtils.java0000644000175000017500000002132511374522114026627 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.util; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.logging.Level; import java.util.logging.Logger; import com.jgoodies.binding.beans.BeanUtils; /** * Assists in logging changes in bound bean properties. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ * * @see Logger */ public final class LoggingUtils { private static final Logger LOGGER = Logger.getLogger(LoggingUtils.class.getName()); private static Level defaultLevel = Level.FINE; private LoggingUtils() { // Override default constructor; prevents instantiation. } /** * Sets the default log level to be used when logging PropertyChangeEvents. * The initial default level is {@link Level#FINE}. * * @param level the default level to be used if no custom level * has been provided * * @throws NullPointerException if the new defaultLevel is null */ public static void setDefaultLevel(Level level) { checkNotNull(level, "The log level must not be null."); LoggingUtils.defaultLevel = level; } /** * Registers a PropertyChangeListener with the specified bean * that logs all PropertyChangeEvents fired by this bean * using the default Logger and default log level. * * @param bean the bean to log PropertyChangeEvents from * * @throws NullPointerException if the bean is null */ public static void logPropertyChanges(Object bean) { logPropertyChanges(bean, LOGGER); } /** * Registers a PropertyChangeListener with the specified bean, which logs * all PropertyChangeEvents fired by the given bean using the specified * Logger and the default log level. * * @param bean the bean to log PropertyChangeEvents from * @param logger the Logger to be used to log PropertyChangeEvents * * @throws NullPointerException if the bean or logger is null */ public static void logPropertyChanges(Object bean, Logger logger) { logPropertyChanges(bean, logger, defaultLevel); } /** * Registers a PropertyChangeListener with the specified bean, which logs * all PropertyChangeEvents fired by the given bean using the specified * Logger and log level. * * @param bean the bean to log PropertyChangeEvents from * @param logger the Logger to be used to log PropertyChangeEvents * @param level the log level * * @throws NullPointerException if the bean, logger, or level is null */ public static void logPropertyChanges(Object bean, Logger logger, Level level) { BeanUtils.addPropertyChangeListener(bean, new LogHandler(logger, level)); } /** * Registers a named PropertyChangeListener with the specified bean, * which logs all PropertyChangeEvents of the given property using * the default Logger and default log level. * * @param bean the bean to log PropertyChangeEvents from * @param propertyName the name of the property which PropertyChangeEvents * should be logged * * @throws NullPointerException if the bean or propertyName is null */ public static void logPropertyChanges(Object bean, String propertyName) { logPropertyChanges(bean, propertyName, LOGGER); } /** * Registers a named PropertyChangeListener with the specified bean, * which logs all PropertyChangeEvents of the given property using * the specified Logger and the default log level. * * @param bean the bean to log PropertyChangeEvents from * @param propertyName the name of the property which PropertyChangeEvents * should be logged * @param logger the Logger to be used to log PropertyChangeEvents * * @throws NullPointerException if the bean, propertyName, or logger is null */ public static void logPropertyChanges(Object bean, String propertyName, Logger logger) { logPropertyChanges(bean, propertyName, logger, defaultLevel); } /** * Registers a named PropertyChangeListener with the specified bean, * which logs all PropertyChangeEvents of the given property, Logger, * and log level. * * @param bean the bean to log PropertyChangeEvents from * @param propertyName the name of the property which PropertyChangeEvents * should be logged * @param logger the Logger to be used to log PropertyChangeEvents * @param level the log level * * @throws NullPointerException if the bean, propertyName, logger, * or level is null */ public static void logPropertyChanges(Object bean, String propertyName, Logger logger, Level level) { BeanUtils.addPropertyChangeListener( bean, propertyName, new LogHandler(logger, level)); } // Helper code ************************************************************ /** * A listener which logs PropertyChangeEvents. */ private static final class LogHandler implements PropertyChangeListener { /** * Logger to be used to log PropertyChangeEvents. */ private final Logger logger; /** * The log level used for the logging. */ private final Level level; /** * Creates and returns LoggingListener, which uses the given Logger * to log PropertyChangeEvents. * * @param logger the logger to be used to log PropertyChangeEvents * @param level the level used for the logging */ LogHandler(Logger logger, Level level) { this.logger = checkNotNull(logger, "The logger must not be null."); this.level = checkNotNull(level, "The level must not be null."); } /** * Logs the given event. */ public void propertyChange(PropertyChangeEvent e) { if (!logger.isLoggable(level)) { return; } Object newValue = e.getNewValue(); Object oldValue = e.getOldValue(); StringBuilder builder = new StringBuilder(e.getSource().toString()); builder.append(" [propertyName="); builder.append(e.getPropertyName()); builder.append(", oldValue="); builder.append(oldValue); if (oldValue != null) { builder.append(", oldValueType="); builder.append(oldValue.getClass()); } builder.append(", newValue="); builder.append(newValue); if (newValue != null) { builder.append(", newValueType="); builder.append(newValue.getClass()); } logger.log(level, builder.toString()); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/util/package.html0000644000175000017500000000467111374522114025643 0ustar twernertwerner Contains binding utility classes.

Related Documentation

For more information see: @see com.jgoodies.binding @see com.jgoodies.binding.adapter @see com.jgoodies.binding.beans @see com.jgoodies.binding.list @see com.jgoodies.binding.value jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/util/ChangeTracker.java0000644000175000017500000002166011374522114026723 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.util; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import com.jgoodies.binding.beans.BeanUtils; import com.jgoodies.binding.beans.Model; import com.jgoodies.binding.beans.PropertyNotBindableException; import com.jgoodies.binding.value.ValueModel; /** * Tracks changes in a set of bound bean properties. The tracker itself * provides a read-only bound bean property changed that indicates * whether one of the observed properties has changed. The changed state * can be reset to {@code false} using #reset.

* * The tracker can observe readable bound bean properties if and only if * the bean provides the optional support for listening on named properties * as described in section 7.4.5 of the * Java Bean * Specification. The bean class must provide the following pair of methods: *

 * public void addPropertyChangeListener(String name, PropertyChangeListener l);
 * public void removePropertyChangeListener(String name, PropertyChangeListener l);
 * 

* * Example:

 * ChangeTracker tracker = new ChangeTracker();
 * tracker.observe(address, "street");
 * tracker.observe(address, "city");
 * tracker.addPropertyChangeListener(new PropertyChangeListener() {
 *
 *     public void propertyChange(PropertyChangeEvent evt) {
 *         System.out.println("Change state: " + evt.getNewValue());
 *     }
 * });
 *
 * // Change the first ValueModel
 * System.out.println(tracker.isChanged()); // Prints "false"
 * address.setStreet("Belsenplatz");        // Prints "Change state: true"
 * System.out.println(tracker.isChanged()); // Prints "true"
 * tracker.reset();                         // Prints "Change state: false"
 * System.out.println(tracker.isChanged()); // Prints "false"
 * 

* * Note: The classes BeanAdapter and * PresentationModel already provide support for tracking changes. * Typical binding code can use these classes and there seems to be no need * to use the ChangeTracker. * * @author Karsten Lentzsch * @version $Revision: 1.7 $ * * @see ValueModel */ public final class ChangeTracker extends Model { /** * The name of the read-only bound bean property that * indicates whether one of the observed properties has changed. * * @see #isChanged() */ public static final String PROPERTYNAME_CHANGED = "changed"; /** * Listens to property changes and updates the changed property. */ private final PropertyChangeListener updateHandler; /** * Indicates whether a registered model has changed. */ private boolean changed = false; // Instance Creation ***************************************************** /** * Constructs a change tracker with change state set to {@code false}. */ public ChangeTracker() { updateHandler = new UpdateHandler(); } // Accessing the Changed State ******************************************** /** * Answers whether one of the registered ValueModels has changed * since this tracker has been reset last time. * * @return true if an observed property has changed since the last reset */ public boolean isChanged() { return changed; } /** * Resets this tracker's changed state to {@code false}. */ public void reset() { setChanged(false); } private void setChanged(boolean newValue) { boolean oldValue = isChanged(); changed = newValue; firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue); } // Registering ************************************************************* /** * Observes the specified readable bound bean property in the given bean. * * @param bean the bean to be observed * @param propertyName the name of the readable bound bean property * @throws NullPointerException if the bean or propertyName is null * @throws PropertyNotBindableException if this tracker can't add * the PropertyChangeListener from the bean * * @see #retractInterestFor(Object, String) */ public void observe(Object bean, String propertyName) { checkNotNull(bean, "The bean must not be null."); checkNotNull(propertyName, "The property name must not be null."); BeanUtils.addPropertyChangeListener(bean, propertyName, updateHandler); } /** * Observes value changes in the given ValueModel. * * @param valueModel the ValueModel to observe * @throws NullPointerException if the valueModel is null * * @see #retractInterestFor(ValueModel) */ public void observe(ValueModel valueModel) { checkNotNull(valueModel, "The ValueModel must not be null."); valueModel.addValueChangeListener(updateHandler); } /** * Retracts interest for the specified readable bound bean property * in the given bean. * * @param bean the bean to be observed * @param propertyName the name of the readable bound bean property * @throws NullPointerException if the bean or propertyName is null * @throws PropertyNotBindableException if this tracker can't remove * the PropertyChangeListener from the bean * * @see #observe(Object, String) */ public void retractInterestFor(Object bean, String propertyName) { checkNotNull(bean, "The bean must not be null."); checkNotNull(propertyName, "The property name must not be null."); BeanUtils.removePropertyChangeListener(bean, propertyName, updateHandler); } /** * Retracts interest for value changes in the given ValueModel. * * @param valueModel the ValueModel to observe * @throws NullPointerException if the valueModel is null * * @see #retractInterestFor(ValueModel) */ public void retractInterestFor(ValueModel valueModel) { checkNotNull(valueModel, "The ValueModel must not be null."); valueModel.removeValueChangeListener(updateHandler); } // Private Helper Code **************************************************** /** * Listens to model changes and updates the changed state. */ private final class UpdateHandler implements PropertyChangeListener { /** * A registered ValueModel has changed. * Updates the changed state. If the property that changed is * 'changed' we assume that this is another changed state and * forward only changes to true. For all other property names, * we just update our changed state to true. * * @param evt the event that describes the property change */ public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); if (!PROPERTYNAME_CHANGED.equals(propertyName) || ((Boolean) evt.getNewValue()).booleanValue()) { setChanged(true); } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/0000755000175000017500000000000011374522114024015 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/RadioButtonAdapter.java0000644000175000017500000002035611374522114030421 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.ButtonGroup; import javax.swing.JToggleButton; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * Converts ValueModels to the ToggleButtonModel interface. Useful to bind * JRadioButtons and JRadioButtonMenuItems to a ValueModel.

* * This adapter holds a choice object that is used to determine * the selection state if the underlying subject ValueModel changes its value. * This model is selected if the subject's value equals the choice object. * And if the selection is set, the choice object is set to the subject.

* * Note: You must not use a ButtonGroup with this adapter. * The RadioButtonAdapter ensures that only one choice is selected by sharing * a single subject ValueModel - at least if all choice values differ. * See also the example below.

* * Example:

 * // Recommended binding style using a factory
 * PresentationModel presentationModel = new PresentationModel(printerSettings);
 * ValueModel orientationModel =
 *     presentationModel.getModel(PrinterSettings.PROPERTYNAME_ORIENTATION);
 * JRadioButton landscapeButton = BasicComponentFactory.createRadioButton(
 *     orientationModel, PrinterSettings.LANDSCAPE, "Landscape");
 * JRadioButton portraitButton  = BasicComponentFactory.createRadioButton(
 *     orientationModel, PrinterSettings.PORTRAIT, "Portrait");
 *
 * // Binding using the Bindings class
 * ValueModel orientationModel =
 *     presentationModel.getModel(PrinterSettings.PROPERTYNAME_ORIENTATION);
 * JRadioButton landscapeButton = new JRadioButton("Landscape");
 * Bindings.bind(landscapeButton, orientationModel, "landscape");
 *
 * JRadioButton portraitButton = new JRadioButton("Portrait");
 * Bindings.bind(portraitButton, orientationModel, "portrait");
 *
 * // Hand-made style
 * ValueModel orientationModel =
 *     presentationModel.getModel(PrinterSettings.PROPERTYNAME_ORIENTATION);
 * JRadioButton landscapeButton = new JRadioButton("Landscape");
 * landscapeButton.setModel(new RadioButtonAdapter(model, "landscape");
 *
 * JRadioButton portraitButton = new JRadioButton("Portrait");
 * portraitButton.setModel(new RadioButtonAdapter(model, "portrait");
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.10 $ * * @see javax.swing.ButtonModel * @see javax.swing.JRadioButton * @see javax.swing.JRadioButtonMenuItem */ public final class RadioButtonAdapter extends JToggleButton.ToggleButtonModel { /** * Refers to the underlying ValueModel that stores the state. */ private final ValueModel subject; /** * Holds the object that is compared with the subject's value * to determine whether this adapter is selected or not. */ private final Object choice; // Instance Creation **************************************************** /** * Constructs a RadioButtonAdapter on the given subject ValueModel * for the specified choice. * The created adapter will be selected if and only if the * subject's initial value equals the given choice. * * @param subject the subject that holds the value * @param choice the choice that indicates that this adapter is selected * * @throws NullPointerException if the subject is {@code null} */ public RadioButtonAdapter(ValueModel subject, Object choice) { this.subject = checkNotNull(subject, "The subject must not be null."); this.choice = choice; subject.addValueChangeListener(new SubjectValueChangeHandler()); updateSelectedState(); } // ToggleButtonModel Implementation *********************************** /** * First, the subject value is set to this adapter's choice value if * the argument is {@code true}. Second, this adapter's state is set * to the then current subject value. The latter ensures that the selection * state is synchronized with the subject - even if the subject rejects * the change.

* * Does nothing if the boolean argument is {@code false}, * or if this adapter is already selected. * * @param b {@code true} sets the choice value as subject value, * and is intended to select this adapter (although it may not happen); * {@code false} does nothing */ @Override public void setSelected(boolean b) { if (!b || isSelected()) { return; } subject.setValue(choice); updateSelectedState(); } // Safety Check *********************************************************** /** * Throws an UnsupportedOperationException if the group * is not {@code null}. You need not and must not * use a ButtonGroup with a set of RadioButtonAdapters. * RadioButtonAdapters form a group by sharing the same * subject ValueModel. * * @param group the ButtonGroup that will be rejected * * @throws UnsupportedOperationException if the group is not {@code null}. */ @Override public void setGroup(ButtonGroup group) { if (group != null) { throw new UnsupportedOperationException( "You need not and must not use a ButtonGroup " + "with a set of RadioButtonAdapters. These form " + "a group by sharing the same subject ValueModel."); } } /** * Updates this adapter's selected state to reflect * whether the subject holds the selected value or not. * Does not modify the subject value. */ private void updateSelectedState() { boolean subjectHoldsChoiceValue = Objects.equals(choice, subject.getValue()); super.setSelected(subjectHoldsChoiceValue); } // Event Handling ********************************************************* /** * Handles changes in the subject's value. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { /** * The subject value has changed. Updates this adapter's selected * state to reflect whether the subject holds the choice value or not. * * @param evt the property change event fired by the subject */ public void propertyChange(PropertyChangeEvent evt) { updateSelectedState(); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/BasicComponentFactory.java0000644000175000017500000010707311374522114031124 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.awt.Color; import java.text.DateFormat; import java.text.Format; import java.text.NumberFormat; import java.text.ParseException; import javax.swing.*; import javax.swing.text.DateFormatter; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.MaskFormatter; import javax.swing.text.NumberFormatter; import com.jgoodies.binding.list.SelectionInList; import com.jgoodies.binding.value.ComponentValueModel; import com.jgoodies.binding.value.ConverterFactory; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.format.EmptyDateFormat; import com.jgoodies.common.format.EmptyNumberFormat; import com.jgoodies.common.swing.MnemonicUtils; /** * Consists only of static methods that create and vend frequently used * Swing components that are then bound to a given ValueModel. * This class is one of two helper classes that help you establish a binding: * 1) the Bindings class binds components that have been created before; * it wraps ValueModels with the adapters from package * com.jgoodies.binding.adapter. * 2) this BasicComponentFactory creates Swing components that are then * tied to ValueModels using the the different #bind methods * in the Bindings class.

* * If you have an existing factory that vends Swing components, you can use * Bindings to bind them to ValueModels. If you don't have such a factory, you * can use this BasicComponentFactory to create and bind Swing components.

* * This class is intended to be used or extended by custom ComponentFactory * classes. Such a factory can create a broader variety of component types, * may use different default configurations, and can use your favorite * Formatters, FormatterFactories, etc. * * @author Karsten Lentzsch * @version $Revision: 1.20 $ * * @see com.jgoodies.binding.value.ValueModel * @see com.jgoodies.binding.adapter.Bindings */ public class BasicComponentFactory { protected BasicComponentFactory() { // Reduce the visibility of the default constructor. } // Factory Methods ******************************************************** /** * Creates and returns a check box with the specified text label * that is bound to the given ValueModel. The check box is selected * if and only if the model's value equals Boolean.TRUE. * The created check box' content area is not filled, and * a mnemonic is set, if the text contains a mnemonic marker * ('&'). See {@link MnemonicUtils} for detailed information * about mnemonic markers and how to quote the marker character.

* * The model is converted to the required ToggleButtonModel * using a ToggleButtonAdapter. * * @param valueModel the model that provides a Boolean value * @param markedText the check boxes' text label * - may contain a mnemonic marker * @return a check box with the specified text bound to the given model, * selected if the model's value equals Boolean.TRUE * * @throws NullPointerException if the valueModel is {@code null} */ public static JCheckBox createCheckBox(ValueModel valueModel, String markedText) { JCheckBox checkBox = new JCheckBox(); checkBox.setContentAreaFilled(false); Bindings.bind(checkBox, valueModel); MnemonicUtils.configure(checkBox, markedText); return checkBox; } /** * Creates and returns a JColorChooser that has the color selection bound * to the given ValueModel. The ValueModel must be of type Color and must * allow read-access to its value, and the initial value must not be * {@code null}.

* * It is strongly recommended (though not required) * that the underlying ValueModel provides only non-null values. * This is so because the ColorSelectionModel behavior is undefined * for {@code null} values and it may have unpredictable results. * To avoid these problems, you may create the ColorChooser with * a default color using {@link #createColorChooser(ValueModel, Color)}. * * @param valueModel a Color-typed ValueModel * @return a color chooser with the selected color bound to the given model * * @throws NullPointerException if the valueModel is {@code null}, * or if its initial value is {@code null} * * @see #createColorChooser(ValueModel, Color) * * @since 1.0.3 */ public static JColorChooser createColorChooser(ValueModel valueModel) { checkNotNull(valueModel, "The valueModel must not be null."); checkNotNull(valueModel.getValue(), "The initial value must not be null."); JColorChooser colorChooser = new JColorChooser( new ColorSelectionAdapter(valueModel)); // Due to a bug in Java 1.4.2, Java 5 and Java 6, we don't use // the Bindings class, but provide a ColorSelectionModel at // instance creation time. The bug is in BasicColorChooserUI // that doesn't listen to color selection model changes. // This is required to update the color preview panel. // But the BasicColorChooserUI registers a preview listener // with the initial color selection model. //Bindings.bind(colorChooser, valueModel); return colorChooser; } /** * Creates and returns a JColorChooser that has the color selection bound * to the given ValueModel. The ValueModel must be of type Color and must * allow read-access to its value. If the valueModel returns * {@code null}, the given default color is used instead. * This avoids problems with the ColorSelectionModel that may have * unpredictable result for {@code null} values. * * @param valueModel a Color-typed ValueModel * @param defaultColor the color used if the valueModel returns null * @return a color chooser with the selected color bound to the given model * * @throws NullPointerException if the valueModel or the default color * is {@code null}, * * @since 1.1 */ public static JColorChooser createColorChooser(ValueModel valueModel, Color defaultColor) { checkNotNull(defaultColor, "The default color must not be null."); JColorChooser colorChooser = new JColorChooser( new ColorSelectionAdapter(valueModel, defaultColor)); // Due to a bug in Java 1.4.2, Java 5 and Java 6, we don't use // the Bindings class, but provide a ColorSelectionModel at // instance creation time. The bug is in BasicColorChooserUI // that doesn't listen to color selection model changes. // This is required to update the color preview panel. // But the BasicColorChooserUI registers a preview listener // with the initial color selection model. //Bindings.bind(colorChooser, valueModel); return colorChooser; } /** * Creates and returns a non-editable JComboBox that is bound * to the given SelectionInList. The SelectionInList's ListModel * is the list data provider and the selection index holder * is used for the combo box model's selected item.

* * If the selectionInList's selection holder is a {@link ComponentValueModel} * it is synchronized with the visible and enabled state of the returned * combo box.

* * There are a couple of other possibilities to bind a JComboBox. * See the constructors and the class comment of the * {@link ComboBoxAdapter}. * * @param selectionInList provides the list and selection * @param the type of the combo box items and the selection * * @return a non-editable JComboBox that is bound to the SelectionInList * * @throws NullPointerException if the selectionInList * is {@code null} * * @see ComboBoxAdapter * * @since 1.0.1 */ public static JComboBox createComboBox(SelectionInList selectionInList) { return createComboBox(selectionInList, null); } /** * Creates and returns a non-editable JComboBox that is bound * to the given SelectionInList using the given cell renderer. * The SelectionInList provides the list data and the selection * index holder is used for the combo box model's selected item.

* * If the selectionInList's selection holder is a {@link ComponentValueModel} * it is synchronized with the visible and enabled state of the returned * combo box.

* * There are a couple of other possibilities to bind a JComboBox. * See the constructors and the class comment of the * {@link ComboBoxAdapter}. * * @param selectionInList provides the list and selection * @param cellRenderer an optional ListCellRenderer, * can be {@code null} * @param the type of the combo box items and the selection * * @return a non-editable JComboBox that is bound to the SelectionInList * and uses the given renderer - if non-{@code null} * * @throws NullPointerException if the selectionInList * is {@code null} * * @see ComboBoxAdapter * * @since 1.0.1 */ public static JComboBox createComboBox(SelectionInList selectionInList, ListCellRenderer cellRenderer) { JComboBox comboBox = new JComboBox(); Bindings.bind(comboBox, selectionInList); if (cellRenderer != null) { comboBox.setRenderer(cellRenderer); } return comboBox; } /** * Creates and returns a formatted text field that is bound * to the Date value of the given ValueModel. * The JFormattedTextField is configured with an AbstractFormatter * that uses two different DateFormats to edit and display the Date. * A SHORT DateFormat with strict checking is used to edit * (parse) a date; the DateFormatter's default DateFormat is used to * display (format) a date. In both cases {@code null} Dates are * mapped to the empty String. * * @param valueModel the model that holds the value to be edited * @return a formatted text field for Date instances that is bound * to the given value model * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createDateField( ValueModel valueModel) { DateFormat shortFormat = DateFormat.getDateInstance(DateFormat.SHORT); shortFormat.setLenient(false); JFormattedTextField.AbstractFormatter defaultFormatter = new DateFormatter(new EmptyDateFormat(shortFormat)); JFormattedTextField.AbstractFormatter displayFormatter = new DateFormatter(new EmptyDateFormat(DateFormat.getDateInstance())); DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(defaultFormatter, displayFormatter); return createFormattedTextField(valueModel, formatterFactory); } /** * Creates and returns a formatted text field that binds its value * to the given model and converts Strings to values using * the given Format. * * @param valueModel the model that provides the value * @param format the Format used to convert values * into a text representation and vice versa via #format * and #parse * @return a formatted text field that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createFormattedTextField( ValueModel valueModel, Format format) { JFormattedTextField textField = new JFormattedTextField(format); Bindings.bind(textField, valueModel); return textField; } /** * Creates and returns a formatted text field that binds its value * to the given model and converts Strings to values using * the given Formatter. * * @param valueModel the model that provides the value * @param formatter the Formatter used to convert values to * a text representation and vice versa via #valueToString * and #stringToValue * @return a formatted text field that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createFormattedTextField( ValueModel valueModel, JFormattedTextField.AbstractFormatter formatter) { JFormattedTextField textField = new JFormattedTextField(formatter); Bindings.bind(textField, valueModel); return textField; } /** * Creates and returns a formatted text field that binds its value * to the given model and converts Strings to values using * Formatters provided by the given AbstractFormatterFactory. * * @param valueModel the model that provides the value * @param formatterFactory provides formatters for different field states * that in turn are used to convert values to a text representation and * vice versa via #valueToString * and #stringToValue * @return a formatted text field that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createFormattedTextField( ValueModel valueModel, JFormattedTextField.AbstractFormatterFactory formatterFactory) { JFormattedTextField textField = new JFormattedTextField(formatterFactory); Bindings.bind(textField, valueModel); return textField; } /** * Creates and returns a formatted text field that binds its value * to the given model and converts Strings to values using * a MaskFormatter that is based on the given mask. * * @param valueModel the model that provides the value * @param mask the mask pattern used to create an instance of * MaskFormatter that in turn converts values to Strings * and vice versa * @return a bound formatted text field using a MaskFormatter * * @throws NullPointerException if the valueModel is {@code null} * @throws IllegalArgumentException if the mask is invalid */ public static JFormattedTextField createFormattedTextField( ValueModel valueModel, String mask) { MaskFormatter formatter = null; try { formatter = new MaskFormatter(mask); } catch (ParseException e) { throw new IllegalArgumentException("Invalid mask '" + mask + "'."); } JFormattedTextField textField = new JFormattedTextField(formatter); Bindings.bind(textField, valueModel); return textField; } // Integer Fields ********************************************************* /** * Creates and returns a formatted text field that is bound * to the Integer value of the given ValueModel. * Empty strings are converted to {@code null} and vice versa.

* * The Format used to convert numbers to strings and vice versa * is NumberFormat.getIntegerInstance(). * * @param valueModel the model that holds the value to be edited * @return a formatted text field for Integer instances that is bound * to the specified valueModel * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createIntegerField( ValueModel valueModel) { return createIntegerField( valueModel, NumberFormat.getIntegerInstance(), null); } /** * Creates and returns a formatted text field that is bound * to the Integer value of the given ValueModel. * Empty strings are converted to the specified empty number.

* * The Format used to convert numbers to strings and vice versa * is NumberFormat.getIntegerInstance(). * * @param valueModel the model that holds the value to be edited * @param emptyNumber an Integer that represents the empty string * @return a formatted text field for Integer instances that is bound * to the specified valueModel * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createIntegerField( ValueModel valueModel, int emptyNumber) { return createIntegerField( valueModel, NumberFormat.getIntegerInstance(), emptyNumber); } /** * Creates and returns a formatted text field that is bound * to the Integer value of the given ValueModel. * Empty strings are converted to {@code null} and vice versa. * * @param valueModel the model that holds the value to be edited * @param numberFormat used to convert numbers to strings and vice versa * @return a formatted text field for Integer instances that is bound * to the specified valueModel * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createIntegerField( ValueModel valueModel, NumberFormat numberFormat) { return createIntegerField( valueModel, numberFormat, null); } /** * Creates and returns a formatted text field that is bound * to the Integer value of the given ValueModel. * Empty strings are converted to the specified empty number. * * @param valueModel the model that holds the value to be edited * @param numberFormat used to convert numbers to strings and vice versa * @param emptyNumber an Integer that represents the empty string * @return a formatted text field for Integer instances that is bound * to the specified valueModel * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createIntegerField( ValueModel valueModel, NumberFormat numberFormat, int emptyNumber) { return createIntegerField( valueModel, numberFormat, Integer.valueOf(emptyNumber)); } /** * Creates and returns a formatted text field that is bound * to the Integer value of the given ValueModel. * Empty strings are converted to the specified empty number. * * @param valueModel the model that holds the value to be edited * @param numberFormat used to convert numbers to strings and vice versa * @param emptyNumber an Integer that represents the empty string * @return a formatted text field for Integer instances that is bound * to the specified valueModel * * @throws NullPointerException if the valueModel is {@code null} */ public static JFormattedTextField createIntegerField( ValueModel valueModel, NumberFormat numberFormat, Integer emptyNumber) { NumberFormatter numberFormatter = new NumberFormatter(new EmptyNumberFormat(numberFormat, emptyNumber)); numberFormatter.setValueClass(Integer.class); return createFormattedTextField(valueModel, numberFormatter); } // Long Fields ************************************************************ /** * Creates and returns a formatted text field that is bound * to the Long value of the given ValueModel. * Empty strings are converted to {@code null} and vice versa.

* * The Format used to convert numbers to strings and vice versa is * NumberFormat.getIntegerInstance(). * * @param valueModel the model that holds the value to be edited * @return a formatted text field for Long instances that is bound to the * specified valueModel * * @throws NullPointerException if the model is {@code null} */ public static JFormattedTextField createLongField(ValueModel valueModel) { return createLongField(valueModel, NumberFormat.getIntegerInstance(), null); } /** * Creates and returns a formatted text field that is bound to the * Long value of the given ValueModel. Empty strings are converted * to the specified empty number.

* * The Format used to convert numbers to strings and vice versa is * NumberFormat.getIntegerInstance(). * * @param valueModel the model that holds the value to be edited * @param emptyNumber a Long that represents the empty string * @return a formatted text field for Long instances that is bound to the * specified valueModel * * @throws NullPointerException if the model is {@code null} */ public static JFormattedTextField createLongField(ValueModel valueModel, long emptyNumber) { return createLongField(valueModel, NumberFormat.getIntegerInstance(), emptyNumber); } /** * Creates and returns a formatted text field that is bound to the * Long value of the given ValueModel. Empty strings are converted * to {@code null} and vice versa. * * @param valueModel the model that holds the value to be edited * @param numberFormat used to convert numbers to strings and vice versa * @return a formatted text field for Long instances that is bound to the * specified valueModel * * @throws NullPointerException if the model is {@code null} */ public static JFormattedTextField createLongField(ValueModel valueModel, NumberFormat numberFormat) { return createLongField(valueModel, numberFormat, null); } /** * Creates and returns a formatted text field that is bound to the * Long value of the given ValueModel. Empty strings are converted * to the specified empty number. * * @param valueModel the model that holds the value to be edited * @param numberFormat used to convert numbers to strings and vice versa * @param emptyNumber a Long that represents the empty string * @return a formatted text field for Long instances that is bound to the * specified valueModel * * @throws NullPointerException if the model is {@code null} */ public static JFormattedTextField createLongField(ValueModel valueModel, NumberFormat numberFormat, long emptyNumber) { return createLongField(valueModel, numberFormat, Long.valueOf(emptyNumber)); } /** * Creates and returns a formatted text field that is bound to the * Long value of the given ValueModel. Empty strings are converted * to the specified empty number. * * @param valueModel the model that holds the value to be edited * @param numberFormat used to convert numbers to strings and vice versa * @param emptyNumber a Long that represents the empty string * @return a formatted text field for Long instances that is bound to the * specified valueModel * * @throws NullPointerException if the model is {@code null} */ public static JFormattedTextField createLongField(ValueModel valueModel, NumberFormat numberFormat, Long emptyNumber) { NumberFormatter numberFormatter = new NumberFormatter(new EmptyNumberFormat(numberFormat, emptyNumber)); numberFormatter.setValueClass(Long.class); return createFormattedTextField(valueModel, numberFormatter); } // ************************************************************************ /** * Creates and returns a text label that is bound to the given ValueModel. * * @param valueModel the model that provides the value * @return a text label that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} */ public static JLabel createLabel(ValueModel valueModel) { JLabel label = new JLabel(); Bindings.bind(label, valueModel); return label; } /** * Creates and returns a text label that is bound to the * given ValueModel that is wrapped by a StringConverter. * The conversion to Strings uses the specified Format. * * @param valueModel the model that provides the value * @param format the format used to create the StringConverter * @return a text label that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} * * @see ConverterFactory */ public static JLabel createLabel(ValueModel valueModel, Format format) { return createLabel(ConverterFactory.createStringConverter(valueModel, format)); } /** * Creates and returns a JList for the given SelectionInList.

* * If the selectionInList's selection holder is a {@link ComponentValueModel} * it is synchronized with the visible and enabled state of the returned * list. * * @param selectionInList provides the list and selection * @param the type of the list items and the selection * @return a JList bound to the given SelectionInList * * @throws NullPointerException if selectionInList is {@code null} */ public static JList createList(SelectionInList selectionInList) { return createList(selectionInList, null); } /** * Creates and returns a JList for the given SelectionInList using * the specified optional ListCellRenderer to render cells.

* * If the selectionInList's selection holder is a {@link ComponentValueModel} * it is synchronized with the visible and enabled state of the returned * list. * * @param selectionInList provides the list and selection * @param cellRenderer an optional ListCellRenderer, * can be {@code null} * @param the type of the list items and the selection * @return a JList bound to the given SelectionInList * * @throws NullPointerException if selectionInList is {@code null} */ public static JList createList(SelectionInList selectionInList, ListCellRenderer cellRenderer) { JList list = new JList(); Bindings.bind(list, selectionInList); if (cellRenderer != null) { list.setCellRenderer(cellRenderer); } return list; } /** * Creates and returns a JPasswordField with the content bound * to the given ValueModel. Text changes are committed to the model * on focus lost.

* * Security Note: The binding created by this method * uses Strings as values of the given ValueModel. The String-typed * passwords could potentially be observed in a security fraud. * For stronger security it is recommended to request a character array * from the JPasswordField and clear the array after use by setting * each character to zero. Method {@link JPasswordField#getPassword()} * return's the field's password as a character array. * * @param valueModel the model that provides the value * @return a text field that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} * * @see #createPasswordField(ValueModel, boolean) * @see JPasswordField#getPassword() */ public static JPasswordField createPasswordField(ValueModel valueModel) { return createPasswordField(valueModel, true); } /** * Creates and returns a JPasswordField with the content bound * to the given ValueModel. Text changes can be committed to the model * on focus lost or on every character typed.

* * Security Note: The binding created by this method * uses Strings as values of the given ValueModel. The String-typed * passwords could potentially be observed in a security fraud. * For stronger security it is recommended to request a character array * from the JPasswordField and clear the array after use by setting * each character to zero. Method {@link JPasswordField#getPassword()} * return's the field's password as a character array. * * @param valueModel the model that provides the value * @param commitOnFocusLost true to commit text changes on focus lost, * false to commit text changes on every character typed * @return a text field that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} * * @see #createPasswordField(ValueModel) * @see JPasswordField#getPassword() */ public static JPasswordField createPasswordField( ValueModel valueModel, boolean commitOnFocusLost) { JPasswordField textField = new JPasswordField(); Bindings.bind(textField, valueModel, commitOnFocusLost); return textField; } /** * Creates and returns a radio button with the specified text label * that is bound to the given ValueModel. The radio button is selected * if and only if the model's value equals the specified choice.. * The created radio buttons' content area is not filled, and * a mnemonic is set, if the text contains a mnemonic marker * ('&'). See {@link MnemonicUtils} for detailed information * about mnemonic markers and how to quote the marker character.

* * The model is converted to the required ToggleButton * using a RadioButtonAdapter. * * @param model the model that provides the current choice * @param choice this button's value * @param markedText the radio buttons' text label * - may contain a mnemonic marker * @return a radio button with the specified text bound to the given model, * selected if the model's value equals the specified choice * * @throws NullPointerException if the valueModel is {@code null} */ public static JRadioButton createRadioButton(ValueModel model, Object choice, String markedText) { JRadioButton radioButton = new JRadioButton(); radioButton.setContentAreaFilled(false); Bindings.bind(radioButton, model, choice); MnemonicUtils.configure(radioButton, markedText); return radioButton; } /** * Creates and returns a text area with the content bound to the given * ValueModel. Text changes are committed to the model on focus lost. * * @param valueModel the model that provides the value * @return a text area that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} * * @see #createTextArea(ValueModel, boolean) */ public static JTextArea createTextArea(ValueModel valueModel) { return createTextArea(valueModel, true); } /** * Creates and returns a text area with the content bound to the given * ValueModel. Text changes can be committed to the model on focus lost * or on every character typed. * * @param valueModel the model that provides the text value * @param commitOnFocusLost true to commit text changes on focus lost, * false to commit text changes on every character typed * @return a text area that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} * * @see #createTextArea(ValueModel) */ public static JTextArea createTextArea( ValueModel valueModel, boolean commitOnFocusLost) { JTextArea textArea = new JTextArea(); Bindings.bind(textArea, valueModel, commitOnFocusLost); return textArea; } /** * Creates and returns a text field with the content bound * to the given ValueModel. Text changes are committed to the model * on focus lost. * * @param valueModel the model that provides the value * @return a text field that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} * * @see #createTextField(ValueModel, boolean) */ public static JTextField createTextField(ValueModel valueModel) { return createTextField(valueModel, true); } /** * Creates and returns a text field with the content bound * to the given ValueModel. Text changes can be committed to the model * on focus lost or on every character typed. * * @param valueModel the model that provides the text value * @param commitOnFocusLost true to commit text changes on focus lost, * false to commit text changes on every character typed * @return a text field that is bound to the given value model * * @throws NullPointerException if the valueModel is {@code null} * * @see #createTextField(ValueModel) */ public static JTextField createTextField( ValueModel valueModel, boolean commitOnFocusLost) { JTextField textField = new JTextField(); Bindings.bind(textField, valueModel, commitOnFocusLost); return textField; } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/SpinnerToValueModelConnector.java0000644000175000017500000002211211374522114032430 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.SpinnerModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.value.ValueModel; /** * Synchronizes a SpinnerModel with a ValueModel. * * @author Karsten Lentzsch * @version $Revision: 1.13 $ * * @see SpinnerModel * @see ValueModel * * @since 1.1 */ public final class SpinnerToValueModelConnector { /** * Holds a SpinnerModel instance that is used to read and set the * spinner value. */ private final SpinnerModel spinnerModel; /** * Holds the underlying ValueModel that is used to read values, to update * the spinner model and to write values if the spinner changes. */ private final ValueModel valueModel; /** * An optional value that will be set to update the spinner model * if the value model's value is {@code null}. */ private final Object defaultValue; /** * Implements PropertyChangeListener and ChangeListener and * is used to update both the SpinnerModel and the ValueModel. */ private final UpdateHandler updateHandler; // Instance Creation ****************************************************** /** * Constructs a SpinnerToValueModelConnector that establishes a * Synchronization between the SpinnerModel and ValueModel. * This constructor does not synchronize the SpinnerModel and ValueModel now. * You may update the spinner model or value model using * #updateSpinnerModel or #updateValueModel.

* * In case you don't need the connector instance, you better use * the static method {@link #connect(SpinnerModel, ValueModel, Object)}. * This constructor may confuse developers if you just use * the side effects performed in the constructor; this is because it is * quite unconventional to instantiate an object that you never use. * * @param spinnerModel the SpinnerModel to be synchronized * @param valueModel the ValueModel to be synchronized * @param defaultValue the value that will be used to update * the spinnerModel, if the valueModel's value is {@code null} * * @throws NullPointerException * if the spinnerModel, valueModel or defaultValue is {@code null} */ public SpinnerToValueModelConnector( SpinnerModel spinnerModel, ValueModel valueModel, Object defaultValue) { this.spinnerModel = checkNotNull(spinnerModel, "The spinner model must not be null."); this.valueModel = checkNotNull(valueModel, "The value model must not be null."); this.defaultValue = checkNotNull(defaultValue, "The default value must not be null."); this.updateHandler = new UpdateHandler(); spinnerModel.addChangeListener(updateHandler); valueModel.addValueChangeListener(updateHandler); } /** * Establishes a synchronization between the SpinnerModel and ValueModel. * This method does not synchronize the SpinnerModel and ValueModel now. * You may update the spinner model or value model using * #updateSpinnerModel or #updateValueModel. * * @param spinnerModel the SpinnerModel to be synchronized * @param valueModel the ValueModel to be synchronized * @param defaultValue the value used if the valueModel's value is {@code null} * * @throws NullPointerException * if the spinnerModel or valueModel is {@code null} */ public static void connect(SpinnerModel spinnerModel, ValueModel valueModel, Object defaultValue) { new SpinnerToValueModelConnector(spinnerModel, valueModel, defaultValue); } // Synchronization ******************************************************** /** * Sets the subject value as spinner value. */ public void updateSpinnerModel() { Object value = valueModel.getValue(); Object valueWithDefault = value != null ? value : defaultValue; setSpinnerModelValueSilently(valueWithDefault); } /** * Sets the spinner value as value model's value. */ public void updateValueModel() { setValueModelValueSilently(spinnerModel.getValue()); } /** * Sets the spinner model's value without notifying the subject of changes. * Invoked by the subject change listener. * * @param newValue the value to be set in the spinner model */ private void setSpinnerModelValueSilently(Object newValue) { spinnerModel.removeChangeListener(updateHandler); spinnerModel.setValue(newValue); spinnerModel.addChangeListener(updateHandler); } /** * Reads the current value from the spinner model and sets it as new * value of the subject. Removes the value change listener before the * subject value is set and adds it after the new value has been set. * * @param newValue the value to be set in the value model */ private void setValueModelValueSilently(Object newValue) { valueModel.removeValueChangeListener(updateHandler); valueModel.setValue(newValue); valueModel.addValueChangeListener(updateHandler); } // Misc ******************************************************************* /** * Removes the internal listener from the SpinnerModel and ValueModel. * This connector must not be used after calling #release.

* * To avoid memory leaks it is recommended to invoke this method, * if the ValueModel lives much longer than the text component. * Instead of releasing this connector, you typically make the ValueModel * obsolete by releasing the PresentationModel or BeanAdapter that has * created the ValueModel.

* * As an alternative you may use ValueModels that in turn use * event listener lists implemented using WeakReference. * * @see PresentationModel#release() * @see BeanAdapter#release() * @see java.lang.ref.WeakReference */ public void release() { spinnerModel.removeChangeListener(updateHandler); valueModel.removeValueChangeListener(updateHandler); } // Event Handling Class *************************************************** /** * Registered with both the SpinnerModel and the ValueModel. * Used to update the spinner if the value changes, and vice versa. */ private final class UpdateHandler implements PropertyChangeListener, ChangeListener { /** * The valueModel's value has changed; update the spinner model. * * @param evt the event to handle */ public void propertyChange(PropertyChangeEvent evt) { updateSpinnerModel(); } /** * The spinner value has changed; update the valueModel and * notify all listeners about the state change. * * @param evt the change event */ public void stateChanged(ChangeEvent evt) { updateValueModel(); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/Bindings.java0000644000175000017500000013600411374522114026421 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotBlank; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.awt.Color; import java.awt.Component; import java.awt.KeyboardFocusManager; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListenerProxy; import java.beans.PropertyChangeSupport; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import javax.swing.*; import javax.swing.text.JTextComponent; import com.jgoodies.binding.beans.PropertyConnector; import com.jgoodies.binding.list.SelectionInList; import com.jgoodies.binding.value.BufferedValueModel; import com.jgoodies.binding.value.ComponentValueModel; import com.jgoodies.binding.value.ValueModel; /** * Consists only of static methods that bind Swing components to ValueModels. * This is one of two helper classes that assist you in establishing a binding: * 1) this Bindings class binds components that have been created before; * it wraps ValueModels with the adapters from package * com.jgoodies.binding.adapter. * 2) the BasicComponentFactory creates Swing components that are then * bound using this Bindings class.

* * If you have an existing factory that vends Swing components, * you can use this Bindings class to bind them to ValueModels. * If you don't have such a factory, you can use the BasicComponentFactory * or a custom subclass to create and bind Swing components.

* * The binding process for JCheckBox, JRadioButton, and other AbstractButtons * retains the former enablement state. Before the new (adapting) model * is set, the enablement is requested from the model, not the button. * This enablement is set after the new model has been set.

* * TODO: Consider adding binding methods for JProgressBar, * JSlider, JSpinner, and JTabbedPane.

* * TODO: Consider adding connection methods for pairs of bean properties. * In addition to the PropertyConnector's {@code connect} method, * this could add boolean operators such as: not, and, or, nor. * * @author Karsten Lentzsch * @version $Revision: 1.38 $ * * @see com.jgoodies.binding.value.ValueModel * @see BasicComponentFactory */ public final class Bindings { /** * A JTextComponent client property key used to store and retrieve * the BufferedValueModel associated with text components that * commit on focus lost. * * @see #bind(JTextArea, ValueModel, boolean) * @see #bind(JTextField, ValueModel, boolean) * @see #flushImmediately() * @see BufferedValueModel */ private static final String COMMIT_ON_FOCUS_LOST_MODEL_KEY = "commitOnFocusListModel"; /** * A JComponent client property key used to store * and retrieve an associated ComponentValueModel. * * @see #addComponentPropertyHandler(JComponent, ValueModel) * @see #removeComponentPropertyHandler(JComponent) * @see ComponentValueModel */ private static final String COMPONENT_VALUE_MODEL_KEY = "componentValueModel"; /** * A JComponent client property key used to store * and retrieve an associated ComponentPropertyHandler. * * @see #addComponentPropertyHandler(JComponent, ValueModel) * @see #removeComponentPropertyHandler(JComponent) */ private static final String COMPONENT_PROPERTY_HANDLER_KEY = "componentPropertyHandler"; /** * Triggers a commit in the shared focus lost trigger * if focus is lost permanently. Shared among all components * that are configured to commit on focus lost. * * @see #createCommitOnFocusLostModel(ValueModel, Component) */ static final FocusLostHandler FOCUS_LOST_HANDLER = new FocusLostHandler(); /** * Holds a weak trigger that is shared by BufferedValueModels * that commit on permanent focus change. * * @see #createCommitOnFocusLostModel(ValueModel, Component) */ static final WeakTrigger FOCUS_LOST_TRIGGER = new WeakTrigger(); private Bindings() { // Suppresses default constructor; prevents instantiation. } // Binding Methods ******************************************************** /** * Binds a JCheckBox to the given ValueModel and retains the enablement * state. The bound check box is selected if and only if the model's value * equals Boolean.TRUE.

* * The value model is converted to the required interface * ToggleButtonModel using a ToggleButtonAdapter. * * @param checkBox the check box to be bound * @param valueModel the model that provides a Boolean value * * @throws NullPointerException if the checkBox or valueModel * is {@code null} */ public static void bind(JCheckBox checkBox, ValueModel valueModel) { ButtonModel oldModel = checkBox.getModel(); String oldActionCommand = oldModel.getActionCommand(); boolean oldEnabled = oldModel.isEnabled(); int oldMnemonic = oldModel.getMnemonic(); int oldMnemonicIndex = checkBox.getDisplayedMnemonicIndex(); ButtonModel newModel = new ToggleButtonAdapter(valueModel); newModel.setActionCommand(oldActionCommand); newModel.setEnabled(oldEnabled); newModel.setMnemonic(oldMnemonic); checkBox.setModel(newModel); checkBox.setDisplayedMnemonicIndex(oldMnemonicIndex); addComponentPropertyHandler(checkBox, valueModel); } /** * Binds a JCheckBoxMenuItem to the given ValueModel and retains * the enablement state. The bound menu item is selected if and only if * the model's value equals Boolean.TRUE.

* * Note: For users of the JGoodies UIF (user interface * framework) the recommended way to create and bind check box menu items * is the class com.jgoodies.uif.ToggleAction.

* * The value model is converted to the required interface * ToggleButtonModel using a ToggleButtonAdapter. * * @param checkBoxMenuItem the check box menu item to be bound * @param valueModel the model that provides a Boolean value * * @throws NullPointerException if the menu item or valueModel * is {@code null} */ public static void bind(JCheckBoxMenuItem checkBoxMenuItem, ValueModel valueModel) { ButtonModel oldModel = checkBoxMenuItem.getModel(); String oldActionCommand = oldModel.getActionCommand(); boolean oldEnabled = oldModel.isEnabled(); int oldMnemonic = oldModel.getMnemonic(); int oldMnemonicIndex = checkBoxMenuItem.getDisplayedMnemonicIndex(); ButtonModel newModel = new ToggleButtonAdapter(valueModel); newModel.setActionCommand(oldActionCommand); newModel.setEnabled(oldEnabled); newModel.setMnemonic(oldMnemonic); checkBoxMenuItem.setModel(newModel); checkBoxMenuItem.setDisplayedMnemonicIndex(oldMnemonicIndex); addComponentPropertyHandler(checkBoxMenuItem, valueModel); } /** * Binds a JColorChooser to the given Color-typed ValueModel. * The ValueModel must be of type Color and must * allow read-access to its value.

* * Also, it is strongly recommended (though not required) that * the underlying ValueModel provides only non-null values. * This is so because the ColorSelectionModel behavior is undefined * for {@code null} values and it may have unpredictable results. * To avoid these problems, you may bind the ColorChooser with * a default color using {@link #bind(JColorChooser, ValueModel, Color)}.

* * Since the color chooser is considered a container, not a single * component, it is not synchronized with the valueModel's * {@link ComponentValueModel} properties - if any.

* * Note: There's a bug in Java 1.4.2, Java 5 and Java 6 * that affects this binding. The BasicColorChooserUI doesn't listen * to changes in the selection model, and so the preview panel won't * update if the selected color changes. As a workaround you can use * {@link BasicComponentFactory#createColorChooser(ValueModel)}, * or you could use a Look&Feel that fixes the bug mentioned above. * * @param colorChooser the color chooser to be bound * @param valueModel the model that provides non-{@code null} * Color values. * * @throws NullPointerException if the color chooser or value model * is {@code null} * * @see #bind(JColorChooser, ValueModel, Color) * * @since 1.0.3 */ public static void bind(JColorChooser colorChooser, ValueModel valueModel) { colorChooser.setSelectionModel(new ColorSelectionAdapter(valueModel)); } /** * Binds a JColorChooser to the given Color-typed ValueModel. * The ValueModel must be of type Color and must allow read-access * to its value. The default color will be used if the valueModel * returns {@code null}.

* * Since the color chooser is considered a container, not a single * component, it is not synchronized with the valueModel's * {@link ComponentValueModel} properties - if any.

* * Note: There's a bug in Java 1.4.2, Java 5 and Java 6 * that affects this binding. The BasicColorChooserUI doesn't listen * to changes in the selection model, and so the preview panel won't * update if the selected color changes. As a workaround you can use * {@link BasicComponentFactory#createColorChooser(ValueModel)}, * or you could use a Look&Feel that fixes the bug mentioned above. * * @param colorChooser the color chooser to be bound * @param valueModel the model that provides non-{@code null} * Color values. * @param defaultColor the color used if the valueModel returns null * * @throws NullPointerException if the color chooser, value model, * or default color is {@code null} * * @since 1.1 */ public static void bind(JColorChooser colorChooser, ValueModel valueModel, Color defaultColor) { checkNotNull(defaultColor, "The default color must not be null."); colorChooser.setSelectionModel(new ColorSelectionAdapter(valueModel, defaultColor)); } /** * Binds a non-editable JComboBox to the given SelectionInList using * the SelectionInList's ListModel as list data provider and the * SelectionInList's selection index holder for the combo box model's * selected item.

* * There are a couple of other possibilities to bind a JComboBox. * See the constructors and the class comment of the * {@link ComboBoxAdapter}.

* * Since version 2.0 the combo's enabled and visible * state is synchronized with the selectionInList's selection holder, * if it's a {@link ComponentValueModel}. * * @param comboBox the combo box to be bound * @param selectionInList provides the list and selection; * if the selection holder is a ComponentValueModel, it is synchronized * with the comboBox properties visible and enabled * @param the type of the combo box items * * @throws NullPointerException if the combo box or the selectionInList * is {@code null} * * @see ComboBoxAdapter * * @since 1.0.1 */ public static void bind(JComboBox comboBox, SelectionInList selectionInList) { checkNotNull(selectionInList, "The SelectionInList must not be null."); comboBox.setModel(new ComboBoxAdapter(selectionInList)); addComponentPropertyHandler(comboBox, selectionInList.getSelectionHolder()); } /** * Binds the given JFormattedTextField to the specified ValueModel. * Synchronized the ValueModel's value with the text field's value * by means of a PropertyConnector. * * @param textField the JFormattedTextField that is to be bound * @param valueModel the model that provides the value * * @throws NullPointerException if the text field or valueModel * is {@code null} */ public static void bind(JFormattedTextField textField, ValueModel valueModel) { bind(textField, "value", valueModel); } /** * Binds the given JLabel to the specified ValueModel. * * @param label a label that shall be bound to the given value model * @param valueModel the model that provides the value * * @throws NullPointerException if the label or valueModel is {@code null} */ public static void bind(JLabel label, ValueModel valueModel) { bind(label, "text", valueModel); } /** * Binds a JList to the given SelectionInList using the SelectionInList's * ListModel as list data provider and the SelectionInList's selection * index holder for the selection model.

* * Since version 2.0 the list's enabled and visible * state is synchronized with the selectionInList's selection holder, * if it's a {@link ComponentValueModel}. * * @param list the list to be bound * @param selectionInList provides the list and selection; * if the selection holder is a ComponentValueModel, it is synchronized * with the list properties visible and enabled * @param the type of the list items * * @throws NullPointerException if the list or the selectionInList * is {@code null} */ public static void bind(JList list, SelectionInList selectionInList) { checkNotNull(selectionInList, "The SelectionInList must not be null."); list.setModel(selectionInList); list.setSelectionModel( new SingleListSelectionAdapter( selectionInList.getSelectionIndexHolder())); addComponentPropertyHandler(list, selectionInList.getSelectionHolder()); } /** * Binds a JRadioButton to the given ValueModel and retains the enablement * state. The bound radio button is selected if and only if the model's * value equals the specified choice value.

* * The model is converted to the required interface * ToggleButtonModel using a RadioButtonAdapter. * * @param radioButton the radio button to be bound to the given model * @param model the model that provides the current choice * @param choice this button's value * * @throws NullPointerException if the valueModel is {@code null} */ public static void bind(JRadioButton radioButton, ValueModel model, Object choice) { ButtonModel oldModel = radioButton.getModel(); String oldActionCommand = oldModel.getActionCommand(); boolean oldEnabled = oldModel.isEnabled(); int oldMnemonic = oldModel.getMnemonic(); int oldMnemonicIndex = radioButton.getDisplayedMnemonicIndex(); ButtonModel newModel = new RadioButtonAdapter(model, choice); newModel.setActionCommand(oldActionCommand); newModel.setEnabled(oldEnabled); newModel.setMnemonic(oldMnemonic); radioButton.setModel(newModel); radioButton.setDisplayedMnemonicIndex(oldMnemonicIndex); addComponentPropertyHandler(radioButton, model); } /** * Binds a JRadioButtonMenuItem to the given ValueModel and retains * the enablement state. The bound menu item is selected if and only if * the model's value equals the specified choice.

* * Note: For users of the JGoodies UIF (user interface * framework) the recommended way to create and bind radio button menu items * is the class com.jgoodies.uif.ToggleAction.

* * The model is converted to the required interface * ToggleButtonModel using a RadioButtonAdapter. * * @param radioButtonMenuItem the radio item to be bound to the given model * @param model the model that provides the current choice * @param choice this button's value * * @throws NullPointerException if the valueModel is {@code null} */ public static void bind( JRadioButtonMenuItem radioButtonMenuItem, ValueModel model, Object choice) { ButtonModel oldModel = radioButtonMenuItem.getModel(); String oldActionCommand = oldModel.getActionCommand(); boolean oldEnabled = oldModel.isEnabled(); int oldMnemonic = oldModel.getMnemonic(); int oldMnemonicIndex = radioButtonMenuItem.getDisplayedMnemonicIndex(); ButtonModel newModel = new RadioButtonAdapter(model, choice); newModel.setActionCommand(oldActionCommand); newModel.setEnabled(oldEnabled); newModel.setMnemonic(oldMnemonic); radioButtonMenuItem.setModel(newModel); radioButtonMenuItem.setDisplayedMnemonicIndex(oldMnemonicIndex); addComponentPropertyHandler(radioButtonMenuItem, model); } /** * Binds a text area to the given ValueModel. * The model is updated on every character typed.

* * TODO: Consider changing the semantics to commit on focus lost. * This would be consistent with the text component vending factory methods * in the BasicComponentFactory that have no boolean parameter. * * @param textArea the text area to be bound to the value model * @param valueModel the model that provides the text value * * @throws NullPointerException if the text component or valueModel * is {@code null} */ public static void bind( JTextArea textArea, ValueModel valueModel) { bind(textArea, valueModel, false); } /** * Binds a text area to the given ValueModel. * The model can be updated on focus lost or on every character typed. * The DocumentAdapter used in this binding doesn't filter newlines. * * @param textArea the text area to be bound to the value model * @param valueModel the model that provides the text value * @param commitOnFocusLost true to commit text changes on focus lost, * false to commit text changes on every character typed * * @throws NullPointerException if the text component or valueModel * is {@code null} */ public static void bind( JTextArea textArea, ValueModel valueModel, boolean commitOnFocusLost) { checkNotNull(valueModel, "The value model must not be null."); ValueModel textModel; if (commitOnFocusLost) { textModel = createCommitOnFocusLostModel(valueModel, textArea); textArea.putClientProperty(COMMIT_ON_FOCUS_LOST_MODEL_KEY, textModel); } else { textModel = valueModel; } TextComponentConnector connector = new TextComponentConnector(textModel, textArea); connector.updateTextComponent(); addComponentPropertyHandler(textArea, valueModel); } /** * Bind a text fields or password field to the given ValueModel. * The model is updated on every character typed.

* * Security Note: If you use this method to bind a * JPasswordField, the field's password will be requested as Strings * from the field and will be held as String by the given ValueModel. * These password String could potentially be observed in a security fraud. * For stronger security it is recommended to request a character array * from the JPasswordField and clear the array after use by setting * each character to zero. Method {@link JPasswordField#getPassword()} * return's the field's password as a character array.

* * TODO: Consider changing the semantics to commit on focus lost. * This would be consistent with the text component vending factory methods * in the BasicComponentFactory that have no boolean parameter. * * @param textField the text field to be bound to the value model * @param valueModel the model that provides the text value * * @throws NullPointerException if the text component or valueModel * is {@code null} * * @see JPasswordField#getPassword() */ public static void bind( JTextField textField, ValueModel valueModel) { bind(textField, valueModel, false); } /** * Binds a text field or password field to the given ValueModel. * The model can be updated on focus lost or on every character typed.

* * Security Note: If you use this method to bind a * JPasswordField, the field's password will be requested as Strings * from the field and will be held as String by the given ValueModel. * These password String could potentially be observed in a security fraud. * For stronger security it is recommended to request a character array * from the JPasswordField and clear the array after use by setting * each character to zero. Method {@link JPasswordField#getPassword()} * return's the field's password as a character array. * * @param textField the text field to be bound to the value model * @param valueModel the model that provides the text value * @param commitOnFocusLost true to commit text changes on focus lost, * false to commit text changes on every character typed * * @throws NullPointerException if the text component or valueModel * is {@code null} * * @see JPasswordField#getPassword() */ public static void bind( JTextField textField, ValueModel valueModel, boolean commitOnFocusLost) { checkNotNull(valueModel, "The value model must not be null."); ValueModel textModel; if (commitOnFocusLost) { textModel = createCommitOnFocusLostModel(valueModel, textField); textField.putClientProperty(COMMIT_ON_FOCUS_LOST_MODEL_KEY, textModel); } else { textModel = valueModel; } TextComponentConnector connector = new TextComponentConnector(textModel, textField); connector.updateTextComponent(); addComponentPropertyHandler(textField, valueModel); } /** * Binds the specified property of the given JComponent to the specified * ValueModel. Synchronizes the ValueModel's value with the component's * property by means of a PropertyConnector. * * @param component the JComponent that is to be bound * @param propertyName the name of the property to be bound * @param valueModel the model that provides the value * * @throws NullPointerException if the component or value model * or property name is {@code null} * @throws IllegalArgumentException if {@code propertyName} is blank or whitespace * * @since 1.2 */ public static void bind(JComponent component, String propertyName, ValueModel valueModel) { checkNotNull(component, "The component must not be null."); checkNotNull(valueModel, "The value model must not be null."); checkNotBlank(propertyName, "The property name must not be null, empty, or whitespace."); PropertyConnector.connectAndUpdate(valueModel, component, propertyName); addComponentPropertyHandler(component, valueModel); } // Updating Component State on ComponentValueModel Changes **************** /** * If the given model is a ComponentValueModel, a component property handler * is registered with this model. This handler updates the component state * if the ComponentValueModel indicates a change in one of its properties, * for example: visible, enabled, and editable.

* * Also the ComponentValueModel and the component handler are stored * as client properties with the component. This way they can be removed * later using #removeComponentPropertyHandler. * * @param component the component where the handler is registered * @param valueModel the model to observe * * @see #removeComponentPropertyHandler(JComponent) * @see ComponentValueModel * * @since 1.1 */ public static void addComponentPropertyHandler(JComponent component, ValueModel valueModel) { if (!(valueModel instanceof ComponentValueModel)) { return; } ComponentValueModel cvm = (ComponentValueModel) valueModel; PropertyChangeListener componentHandler = new ComponentPropertyHandler(component); cvm.addPropertyChangeListener(componentHandler); component.putClientProperty(COMPONENT_VALUE_MODEL_KEY, cvm); component.putClientProperty(COMPONENT_PROPERTY_HANDLER_KEY, componentHandler); component.setEnabled(cvm.isEnabled()); component.setVisible(cvm.isVisible()); if (component instanceof JTextComponent) { ((JTextComponent) component).setEditable(cvm.isEditable()); } } /** * If the given component holds a ComponentValueModel and * a ComponentPropertyHandler in its client properties, * the handler is removed as listener from the model, * and the model and handler are removed from the client properties. * * @param component the component to remove the listener from * * @see #addComponentPropertyHandler(JComponent, ValueModel) * @see ComponentValueModel * * @since 1.1 */ public static void removeComponentPropertyHandler(JComponent component) { ComponentValueModel componentValueModel = (ComponentValueModel) component.getClientProperty( COMPONENT_VALUE_MODEL_KEY); PropertyChangeListener componentHandler = (PropertyChangeListener) component.getClientProperty( COMPONENT_PROPERTY_HANDLER_KEY); if (componentValueModel != null && componentHandler != null) { componentValueModel.removePropertyChangeListener(componentHandler); component.putClientProperty(COMPONENT_VALUE_MODEL_KEY, null); component.putClientProperty(COMPONENT_PROPERTY_HANDLER_KEY, null); } else if (componentValueModel == null && componentHandler == null) { return; } else if (componentValueModel != null) { throw new IllegalStateException( "The component has a ComponentValueModel stored, " + "but lacks the ComponentPropertyHandler."); } else { throw new IllegalStateException( "The component has a ComponentPropertyHandler stored, " + "but lacks the ComponentValueModel."); } } // Misc ******************************************************************* /** * Commits a pending edit - if any. Useful to ensure that edited values * in bound text components that commit on focus-lost are committed * before an operation is performed that uses the value to be committed * after a focus lost.

* * For example, before you save a form, a value that has been edited * shall be committed, so the validation can check whether the save * is allowed or not. * * @since 1.2 */ public static void commitImmediately() { FOCUS_LOST_TRIGGER.triggerCommit(); } /** * Flushes a pending edit in the focused text component - if any. * Useful to revert edited values in bound text components that * commit on focus-lost. This operation can be performed on an escape * key event like the Cancel action in the JFormattedTextField.

* * Returns whether an edit has been reset. Useful to decide whether * a key event shall be consumed or not. * * @return {@code true} if a pending edit has been reset, * {@code false} if the focused component isn't buffering or * doesn't buffer at all * * @see #isFocusOwnerBuffering() * * @since 2.0.1 */ public static boolean flushImmediately() { boolean buffering = isFocusOwnerBuffering(); if (buffering) { FOCUS_LOST_TRIGGER.triggerFlush(); } return buffering; } /** * Checks and answers whether the focus owner is a component * that buffers a pending edit. Useful to enable or disable * a text component Action that resets the edited value.

* * See also the JFormattedTextField's internal {@code CancelAction}. * * @return {@code true} if the focus owner is a JTextComponent * that commits on focus-lost and is buffering * * @see #flushImmediately() * * @since 2.0.1 */ public static boolean isFocusOwnerBuffering() { Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); if (!(focusOwner instanceof JComponent)) { return false; } Object value = ((JComponent) focusOwner).getClientProperty( COMMIT_ON_FOCUS_LOST_MODEL_KEY); if (!(value instanceof BufferedValueModel)) { return false; } BufferedValueModel commitOnFocusLostModel = (BufferedValueModel) value; return commitOnFocusLostModel.isBuffering(); } // Helper Code ************************************************************ /** * Creates and returns a ValueModel that commits its value * if the given component looses the focus permanently. * It wraps the underlying ValueModel with a BufferedValueModel * and delays the value commit until this class' shared FOCUS_LOST_TRIGGER * commits. This happens, because this class' shared FOCUS_LOST_HANDLER * is registered with the specified component. * * @param valueModel the model that provides the value * @param component the component that looses the focus * @return a buffering ValueModel that commits on focus lost * * @throws NullPointerException if the value model is {@code null} */ private static ValueModel createCommitOnFocusLostModel( ValueModel valueModel, Component component) { checkNotNull(valueModel, "The value model must not be null."); ValueModel model = new BufferedValueModel(valueModel, FOCUS_LOST_TRIGGER); component.addFocusListener(FOCUS_LOST_HANDLER); return model; } // Helper Classes ********************************************************* /** * Triggers a commit event on permanent focus lost. */ private static final class FocusLostHandler extends FocusAdapter { /** * Triggers a commit event if the focus lost is permanent. * * @param evt the focus lost event */ @Override public void focusLost(FocusEvent evt) { if (!evt.isTemporary()) { FOCUS_LOST_TRIGGER.triggerCommit(); } } } /** * Listens to property changes in a ComponentValueModel and * updates the associated component state. * * @see ComponentValueModel */ private static final class ComponentPropertyHandler implements PropertyChangeListener { private final JComponent component; private ComponentPropertyHandler(JComponent component) { this.component = component; } public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); ComponentValueModel model = (ComponentValueModel) evt.getSource(); if (ComponentValueModel.PROPERTYNAME_ENABLED.equals(propertyName)) { component.setEnabled(model.isEnabled()); } else if (ComponentValueModel.PROPERTYNAME_VISIBLE.equals(propertyName)) { component.setVisible(model.isVisible()); } else if (ComponentValueModel.PROPERTYNAME_EDITABLE.equals(propertyName)) { if (component instanceof JTextComponent) { ((JTextComponent) component).setEditable(model.isEditable()); } } } } // Helper Code for a Weak Trigger ***************************************** /** * Unlike the Trigger class, this implementation uses WeakReferences * to store value change listeners. */ private static final class WeakTrigger implements ValueModel { private final transient WeakPropertyChangeSupport changeSupport; private Boolean value; // Instance Creation ****************************************************** /** * Constructs a WeakTrigger set to neutral. */ WeakTrigger() { value = null; changeSupport = new WeakPropertyChangeSupport(this); } // ValueModel Implementation ********************************************** /** * Returns a Boolean that indicates the current trigger state. * * @return a Boolean that indicates the current trigger state */ public Object getValue() { return value; } /** * Sets a new Boolean value and rejects all non-Boolean values. * Fires no change event if the new value is equal to the * previously set value.

* * This method is not intended to be used by API users. * Instead you should trigger commit and flush events by invoking * #triggerCommit or #triggerFlush. * * @param newValue the Boolean value to be set * @throws IllegalArgumentException if the newValue is not a Boolean */ public void setValue(Object newValue) { checkArgument(newValue == null || newValue instanceof Boolean, "Trigger values must be of type Boolean."); Object oldValue = value; value = (Boolean) newValue; fireValueChange(oldValue, newValue); } // Change Management **************************************************** /** * Registers the given PropertyChangeListener with this model. * The listener will be notified if the value has changed.

* * The PropertyChangeEvents delivered to the listener have the name * set to "value". In other words, the listeners won't get notified * when a PropertyChangeEvent is fired that has a null object as * the name to indicate an arbitrary set of the event source's * properties have changed.

* * In the rare case, where you want to notify a PropertyChangeListener * even with PropertyChangeEvents that have no property name set, * you can register the listener with #addPropertyChangeListener, * not #addValueChangeListener. * * @param listener the listener to add * * @see ValueModel */ public void addValueChangeListener(PropertyChangeListener listener) { if (listener == null) { return; } changeSupport.addPropertyChangeListener("value", listener); } /** * Removes the given PropertyChangeListener from the model. * * @param listener the listener to remove */ public void removeValueChangeListener(PropertyChangeListener listener) { if (listener == null) { return; } changeSupport.removePropertyChangeListener("value", listener); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the value before the change * @param newValue the value after the change * * @see java.beans.PropertyChangeSupport */ private void fireValueChange(Object oldValue, Object newValue) { changeSupport.firePropertyChange("value", oldValue, newValue); } // Triggering ************************************************************* /** * Triggers a commit event in models that share this Trigger. * Sets the value to {@code Boolean.TRUE} and ensures that listeners * are notified about a value change to this new value. If necessary * the value is temporarily set to {@code null}. This way it minimizes * the number of PropertyChangeEvents fired by this Trigger. */ void triggerCommit() { if (Boolean.TRUE.equals(getValue())) { setValue(null); } setValue(Boolean.TRUE); } /** * Triggers a flush event in models that share this Trigger. * Sets the value to {@code Boolean.FALSE} and ensures that listeners * are notified about a value change to this new value. If necessary * the value is temporarily set to {@code null}. This way it minimizes * the number of PropertyChangeEvents fired by this Trigger. */ void triggerFlush() { if (Boolean.FALSE.equals(getValue())) { setValue(null); } setValue(Boolean.FALSE); } } /** * Differs from its superclass {@link PropertyChangeSupport} in that it * uses WeakReferences for registering listeners. It wraps registered * PropertyChangeListeners with instances of WeakPropertyChangeListener * and cleans up a list of stale references when firing an event.

* * TODO: Merge this WeakPropertyChangeSupport with the * ExtendedPropertyChangeSupport. */ private static final class WeakPropertyChangeSupport extends PropertyChangeSupport { // Instance Creation ****************************************************** /** * Constructs a WeakPropertyChangeSupport object. * * @param sourceBean The bean to be given as the source for any events. */ WeakPropertyChangeSupport( Object sourceBean) { super(sourceBean); } // Managing Property Change Listeners ********************************** /** {@inheritDoc} */ @Override public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { if (listener == null) { return; } if (listener instanceof PropertyChangeListenerProxy) { PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener; // Call two argument add method. addPropertyChangeListener(proxy.getPropertyName(), (PropertyChangeListener) proxy.getListener()); } else { super.addPropertyChangeListener(new WeakPropertyChangeListener(listener)); } } /** {@inheritDoc} */ @Override public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { if (listener == null) { return; } super.addPropertyChangeListener(propertyName, new WeakPropertyChangeListener(propertyName, listener)); } /** {@inheritDoc} */ @Override public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { if (listener == null) { return; } if (listener instanceof PropertyChangeListenerProxy) { PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener; // Call two argument remove method. removePropertyChangeListener(proxy.getPropertyName(), (PropertyChangeListener) proxy.getListener()); return; } PropertyChangeListener[] listeners = getPropertyChangeListeners(); WeakPropertyChangeListener wpcl; for (int i = listeners.length - 1; i >= 0; i--) { if (listeners[i] instanceof PropertyChangeListenerProxy) { continue; } wpcl = (WeakPropertyChangeListener) listeners[i]; if (wpcl.get() == listener) { // TODO: Should we call here the #clear() method of wpcl??? super.removePropertyChangeListener(wpcl); break; } } } /** {@inheritDoc} */ @Override public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { if (listener == null) { return; } PropertyChangeListener[] listeners = getPropertyChangeListeners(propertyName); WeakPropertyChangeListener wpcl; for (int i = listeners.length - 1; i >= 0; i--) { wpcl = (WeakPropertyChangeListener) listeners[i]; if (wpcl.get() == listener) { // TODO: Should we call here the #clear() method of wpcl??? super.removePropertyChangeListener(propertyName, wpcl); break; } } } // Firing Events ********************************************************** /** * Fires the specified PropertyChangeEvent to any registered listeners. * * @param evt The PropertyChangeEvent object. * * @see PropertyChangeSupport#firePropertyChange(PropertyChangeEvent) */ @Override public void firePropertyChange(PropertyChangeEvent evt) { cleanUp(); super.firePropertyChange(evt); } /** * Reports a bound property update to any registered listeners. * * @param propertyName The programmatic name of the property * that was changed. * @param oldValue The old value of the property. * @param newValue The new value of the property. * * @see PropertyChangeSupport#firePropertyChange(String, Object, Object) */ @Override public void firePropertyChange( String propertyName, Object oldValue, Object newValue) { cleanUp(); super.firePropertyChange(propertyName, oldValue, newValue); } static final ReferenceQueue QUEUE = new ReferenceQueue(); private static void cleanUp() { WeakPropertyChangeListener wpcl; while ((wpcl = (WeakPropertyChangeListener) QUEUE.poll()) != null) { wpcl.removeListener(); } } void removeWeakPropertyChangeListener(WeakPropertyChangeListener l) { if (l.propertyName == null) { super.removePropertyChangeListener(l); } else { super.removePropertyChangeListener(l.propertyName, l); } } /** * Wraps a PropertyChangeListener to make it weak. */ private final class WeakPropertyChangeListener extends WeakReference implements PropertyChangeListener { final String propertyName; private WeakPropertyChangeListener(PropertyChangeListener delegate) { this(null, delegate); } private WeakPropertyChangeListener(String propertyName, PropertyChangeListener delegate) { super(delegate, QUEUE); this.propertyName = propertyName; } /** {@inheritDoc} */ public void propertyChange(PropertyChangeEvent evt) { PropertyChangeListener delegate = get(); if (delegate != null) { delegate.propertyChange(evt); } } void removeListener() { removeWeakPropertyChangeListener(this); } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/SpinnerAdapterFactory.java0000644000175000017500000002663011374522114031136 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.util.Calendar; import java.util.Date; import javax.swing.SpinnerDateModel; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import com.jgoodies.binding.value.ValueModel; /** * A factory that vends {@link SpinnerModel} implementations that are bound to * a ValueModel. Can be used to bind a ValueModel to instances of JSpinner.

* * To keep the ValueModel and SpinnerModel synchronized, this class listens * to changes in both sides and updates the other silently, i.e. without * firing a duplicate change event.

* * Constraints: * The ValueModel's type must be compatible with the type required by the * referenced SpinnerModel. For example a {@link SpinnerNumberModel} requires * Number values. * * Example:

 * // General Connection
 * ValueModel   levelModel   = new PropertyAdapter(settings, "level", true);
 * SpinnerModel spinnerModel = new SpinnerNumberModel(9, 5, 10, 1);
 * Object defaultValue       = new Integer(9);
 * SpinnerAdapterFactory.connect(spinnerModel, levelModel, defaultValue);
 * JSpinner levelSpinner = new JSpinner(spinnerModel);
 *
 * // Short Form
 * ValueModel levelModel = new PropertyAdapter(settings, "level", true);
 * SpinnerNumberModel spinnerModel =
 *     SpinnerAdapterFactory.createNumberAdapter(levelModel, 5, 10, 1);
 * JSpinner levelSpinner = new JSpinner(spinnerModel);
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.15 $ * * @see ValueModel * @see SpinnerModel * @see javax.swing.JSpinner * * @since 1.1 */ public final class SpinnerAdapterFactory { private SpinnerAdapterFactory() { // Override default constructor; prevents instantiation. } // Factory Methods ******************************************************** /** * Creates and returns a SpinnerDateModel bound to the given * valueModel. The calendarField * is equal to Calendar.DAY_OF_MONTH; there are no * start/end limits. * * @param valueModel a Date typed model that holds the spinner value * @param defaultDate the date used if the valueModel's value is {@code null} * @return a SpinnerDateModel bound to the given * valueModel without start and end limits using * Calendar.DAY_OF_MONTH as calendar field * * @throws NullPointerException if the valueModel or defaultDate is {@code null} */ public static SpinnerDateModel createDateAdapter(ValueModel valueModel, Date defaultDate) { return createDateAdapter(valueModel, defaultDate, null, null, Calendar.DAY_OF_MONTH); } /** * Creates and returns a SpinnerDateModel that represents a sequence * of dates and is bound to the given valueModel. * The dates are between start and end. The * nextValue and previousValue methods * compute elements of the sequence by advancing or reversing * the current date value by the * calendarField time unit. For a precise description * of what it means to increment or decrement a Calendar * field, see the add method in * java.util.Calendar.

* * The start and end parameters can be * {@code null} to indicate that the range doesn't have an * upper or lower bound. If value or * calendarField is {@code null}, or if both * start and end are specified and * minimum > maximum then an * IllegalArgumentException is thrown. * Similarly if (minimum <= value <= maximum) is false, * an IllegalArgumentException is thrown.

* * This method has not been tested. * * @param valueModel a Date typed model that holds the spinner value * @param defaultDate the date used if the valueModel's value is {@code null} * @param start the first date in the sequence or {@code null} * @param end the last date in the sequence or {@code null} * @param calendarField one of *

    *
  • Calendar.ERA *
  • Calendar.YEAR *
  • Calendar.MONTH *
  • Calendar.WEEK_OF_YEAR *
  • Calendar.WEEK_OF_MONTH *
  • Calendar.DAY_OF_MONTH *
  • Calendar.DAY_OF_YEAR *
  • Calendar.DAY_OF_WEEK *
  • Calendar.DAY_OF_WEEK_IN_MONTH *
  • Calendar.AM_PM *
  • Calendar.HOUR *
  • Calendar.HOUR_OF_DAY *
  • Calendar.MINUTE *
  • Calendar.SECOND *
  • Calendar.MILLISECOND *
* @return a SpinnerDateModel bound to the given * valueModel using the specified start and end dates * and calendar field. * * @throws NullPointerException if the valueModel or defaultDate is {@code null} * @throws IllegalArgumentException if calendarField isn't valid, * or if the following expression is * false: (start <= value <= end). * * @see java.util.Calendar * @see Date */ public static SpinnerDateModel createDateAdapter( ValueModel valueModel, Date defaultDate, Comparable start, Comparable end, int calendarField) { checkNotNull(valueModel, "The valueModel must not be null."); checkNotNull(defaultDate, "The default date must not be null."); Date valueModelDate = (Date) valueModel.getValue(); Date initialDate = valueModelDate != null ? valueModelDate : defaultDate; SpinnerDateModel spinnerModel = new SpinnerDateModel(initialDate, start, end, calendarField); connect(spinnerModel, valueModel, defaultDate); return spinnerModel; } /** * Creates and returns a {@link SpinnerNumberModel} that is connected to * the given {@link ValueModel} and that honors the specified minimum, * maximum and step values. * * @param valueModel an Integer typed model that holds the spinner value * @param defaultValue the number used if the valueModel's value is {@code null} * @param minValue the lower bound of the spinner number * @param maxValue the upper bound of the spinner number * @param stepSize used to increment and decrement the current value * @return a SpinnerNumberModel that is connected to the given * ValueModel * * @throws NullPointerException if the valueModel is {@code null} */ public static SpinnerNumberModel createNumberAdapter( ValueModel valueModel, int defaultValue, int minValue, int maxValue, int stepSize) { return createNumberAdapter(valueModel, Integer.valueOf(defaultValue), Integer.valueOf(minValue), Integer.valueOf(maxValue), Integer.valueOf(stepSize)); } /** * Creates and returns a {@link SpinnerNumberModel} that is connected to * the given {@link ValueModel} and that honors the specified minimum, * maximum and step values. * * @param valueModel a Number typed model that holds the spinner value * @param defaultValue the number used if the valueModel's value is {@code null} * @param minValue the lower bound of the spinner number * @param maxValue the upper bound of the spinner number * @param stepSize used to increment and decrement the current value * @return a SpinnerNumberModel that is connected to the given * ValueModel * * @throws NullPointerException if the valueModel or defaultValue is {@code null} */ public static SpinnerNumberModel createNumberAdapter( ValueModel valueModel, Number defaultValue, Comparable minValue, Comparable maxValue, Number stepSize) { Number valueModelNumber = (Number) valueModel.getValue(); Number initialValue = valueModelNumber != null ? valueModelNumber : defaultValue; SpinnerNumberModel spinnerModel = new SpinnerNumberModel( initialValue, minValue, maxValue, stepSize); connect(spinnerModel, valueModel, defaultValue); return spinnerModel; } // Connecting a ValueModel with a General SpinnerModel********************* /** * Connects the given ValueModel and SpinnerModel * by synchronizing their values. * * @param spinnerModel the underlying SpinnerModel implementation * @param valueModel provides a value * @param defaultValue the value used if the valueModel's value is {@code null} * @throws NullPointerException * if the spinnerModel, valueModel or defaultValue is {@code null} */ public static void connect(SpinnerModel spinnerModel, ValueModel valueModel, Object defaultValue) { new SpinnerToValueModelConnector(spinnerModel, valueModel, defaultValue); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/AbstractTableAdapter.java0000644000175000017500000002550611374522114030704 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.table.AbstractTableModel; /** * An abstract implementation of the {@link javax.swing.table.TableModel} * interface that converts a {@link javax.swing.ListModel} of row elements.

* * This class provides default implementations for the TableModel * methods #getColumnCount() and #getColumnName(int). * To use these methods you must use the constructor that accepts an * array of column names and this array must not be {@code null}. * If a subclass constructs itself with the column names set to {@code null} * it must override the methods #getColumnCount() and * #getColumnName(int).

* * Example: API users subclass AbstractTableAdapter * and just implement the method TableModel#getValueAt(int, int).

* * The following example implementation is based on a list of customer rows * and exposes the first and last name as well as the customer ages:

 * public class CustomerTableModel extends AbstractTableAdapter {
 *
 *     private static final String[] COLUMN_NAMES =
 *         { "Last Name", "First Name", "Age" };
 *
 *     public CustomerTableModel(ListModel listModel) {
 *         super(listModel, COLUMN_NAMES);
 *     }
 *
 *     public Object getValueAt(int rowIndex, int columnIndex) {
 *         Customer customer = (Customer) getRow(rowIndex);
 *         switch (columnIndex) {
 *             case 0 : return customer.getLastName();
 *             case 1 : return customer.getFirstName();
 *             case 2 : return customer.getAge();
 *             default: return null;
 *         }
 *     }
 *
 * }
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.12 $ * * @see javax.swing.ListModel * @see javax.swing.JTable * * @param the type of the ListModel elements */ public abstract class AbstractTableAdapter extends AbstractTableModel { /** * Refers to the ListModel that holds the table row elements * and reports changes in the structure and content. The elements of * the list model can be requested using #getRow(int). * A typical subclass will use the elements to implement the * TableModel method #getValueAt(int, int). * * @see #getRow(int) * @see #getRowCount() * @see javax.swing.table.TableModel#getValueAt(int, int) */ private final ListModel listModel; /** * Holds an optional array of column names that is used by the * default implementation of the TableModel methods * #getColumnCount() and #getColumnName(int). * * @see #getColumnCount() * @see #getColumnName(int) */ private final String[] columnNames; // Instance Creation ****************************************************** /** * Constructs an AbstractTableAdapter on the given ListModel. * Subclasses that use this constructor must override the methods * #getColumnCount() and #getColumnName(int). * * @param listModel the ListModel that holds the row elements * @throws NullPointerException if the list model is {@code null} */ public AbstractTableAdapter(ListModel listModel) { this(listModel, (String[]) null); } /** * Constructs an AbstractTableAdapter on the given ListModel using * the specified table column names. If the column names array is * non-{@code null}, it is copied to avoid external mutation.

* * Subclasses that invoke this constructor with a {@code null} column * name array must override the methods #getColumnCount() and * #getColumnName(int). * * @param listModel the ListModel that holds the row elements * @param columnNames optional column names * @throws NullPointerException if the list model is {@code null} */ public AbstractTableAdapter(ListModel listModel, String... columnNames) { this.listModel = checkNotNull(listModel, "The list model must not be null."); if (columnNames == null || columnNames.length == 0) { this.columnNames = null; } else { this.columnNames = new String[columnNames.length]; System.arraycopy(columnNames, 0, this.columnNames, 0, columnNames.length); } listModel.addListDataListener(createChangeHandler()); } // TableModel Implementation ********************************************** /** * Returns the number of columns in the model. A JTable uses * this method to determine how many columns it should create and * display by default.

* * Subclasses must override this method if they don't provide an * array of column names in the constructor. * * @return the number of columns in the model * @throws NullPointerException if the optional column names array * has not been set in the constructor. In this case API users * must override this method. * * @see #getColumnName(int) * @see #getRowCount() */ public int getColumnCount() { return columnNames.length; } /** * Returns the name of the column at the given column index. * This is used to initialize the table's column header name. * Note: this name does not need to be unique; two columns in a table * can have the same name.

* * Subclasses must override this method if they don't provide an * array of column names in the constructor. * * @param columnIndex the index of the column * @return the name of the column * @throws NullPointerException if the optional column names array * has not been set in the constructor. In this case API users * must override this method. * * @see #getColumnCount() * @see #getRowCount() */ @Override public String getColumnName(int columnIndex) { return columnNames[columnIndex]; } /** * Returns the number of rows in the model. A * JTable uses this method to determine how many rows it * should display. This method should be quick, as it * is called frequently during rendering. * * @return the number of rows in the model * * @see #getRow(int) */ public final int getRowCount() { return listModel.getSize(); } // Misc ******************************************************************* /** * Returns the row at the specified row index. * * @param index row index in the underlying list model * @return the row at the specified row index. */ public final E getRow(int index) { return (E) listModel.getElementAt(index); } // Event Handling ********************************************************* /** * Creates and returns a listener that handles changes * in the underlying list model. * * @return the listener that handles changes in the underlying ListModel */ protected ListDataListener createChangeHandler() { return new ListDataChangeHandler(); } /** * Listens to subject changes and fires a contents change event. */ private final class ListDataChangeHandler implements ListDataListener { /** * Sent after the indices in the index0,index1 * interval have been inserted in the data model. * The new interval includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalAdded(ListDataEvent evt) { fireTableRowsInserted(evt.getIndex0(), evt.getIndex1()); } /** * Sent after the indices in the index0,index1 interval * have been removed from the data model. The interval * includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalRemoved(ListDataEvent evt) { fireTableRowsDeleted(evt.getIndex0(), evt.getIndex1()); } /** * Sent when the contents of the list has changed in a way * that's too complex to characterize with the previous * methods. For example, this is sent when an item has been * replaced. Index0 and index1 bracket the change. * * @param evt a ListDataEvent encapsulating the * event information */ public void contentsChanged(ListDataEvent evt) { int firstRow = evt.getIndex0(); int lastRow = evt.getIndex1(); fireTableRowsUpdated(firstRow, lastRow); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/SingleListSelectionAdapter.java0000644000175000017500000005217611374522114032117 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.ListSelectionModel; import javax.swing.event.EventListenerList; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import com.jgoodies.binding.value.ValueModel; /** * A {@link ListSelectionModel} implementation that has the list index * bound to a {@link ValueModel}. Therefore this class supports only * the SINGLE_SELECTION mode where only one list index * can be selected at a time. In this mode the setSelectionInterval * and addSelectionInterval methods are equivalent, and only * the second index argument (the "lead index") is used.

* * Example:

 * SelectionInList selectionInList = new SelectionInList(...);
 * JList list = new JList();
 * list.setModel(selectionInList);
 * list.setSelectionModel(new SingleListSelectionAdapter(
 *               selectionInList.getSelectionIndexHolder()));
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @see ValueModel * @see javax.swing.JList * @see javax.swing.JTable */ public final class SingleListSelectionAdapter implements ListSelectionModel { private static final int MIN = -1; private static final int MAX = Integer.MAX_VALUE; private int firstAdjustedIndex = MAX; private int lastAdjustedIndex = MIN; private int firstChangedIndex = MAX; private int lastChangedIndex = MIN; /** * Refers to the selection index holder. */ private final ValueModel selectionIndexHolder; /** * Indicates if the selection is undergoing a series of changes. */ private boolean valueIsAdjusting; /** * Holds the List of ListSelectionListener. * * @see #addListSelectionListener(ListSelectionListener) * @see #removeListSelectionListener(ListSelectionListener) */ private final EventListenerList listenerList = new EventListenerList(); // Instance Creation **************************************************** /** * Constructs a SingleListSelectionAdapter with * the given selection index holder. * * @param selectionIndexHolder holds the selection index */ public SingleListSelectionAdapter(ValueModel selectionIndexHolder) { this.selectionIndexHolder = selectionIndexHolder; this.selectionIndexHolder .addValueChangeListener(new SelectionIndexChangeHandler()); } // Accessing the Selection Index **************************************** /** * Returns the selection index. * * @return the selection index */ private int getSelectionIndex() { Object value = selectionIndexHolder.getValue(); return (value == null) ? MIN : ((Integer) value).intValue(); } /** * Sets a new selection index. Does nothing if it is the same as before. * If this represents a change to the current selection then * notify each ListSelectionListener. * * @param newSelectionIndex the selection index to be set */ private void setSelectionIndex(int newSelectionIndex) { setSelectionIndex(getSelectionIndex(), newSelectionIndex); } private void setSelectionIndex(int oldSelectionIndex, int newSelectionIndex) { if (oldSelectionIndex == newSelectionIndex) { return; } markAsDirty(oldSelectionIndex); markAsDirty(newSelectionIndex); selectionIndexHolder.setValue(Integer.valueOf(newSelectionIndex)); fireValueChanged(); } // ListSelectionModel Implementation ************************************ /** * Sets the selection index to index1. Since this model * supports only a single selection index, the index0 is ignored. * This is the behavior the DefaultListSelectionModel * uses in single selection mode.

* * If this represents a change to the current selection, then * notify each ListSelectionListener. Note that index0 doesn't have * to be less than or equal to index1. * * @param index0 one end of the interval. * @param index1 other end of the interval * @see #addListSelectionListener(ListSelectionListener) */ public void setSelectionInterval(int index0, int index1) { if ((index0 == -1) || (index1 == -1)) { return; } setSelectionIndex(index1); } /** * Sets the selection interval using the given indices.

* * If this represents a change to the current selection, then * notify each ListSelectionListener. Note that index0 doesn't have * to be less than or equal to index1. * * @param index0 one end of the interval. * @param index1 other end of the interval * @see #addListSelectionListener(ListSelectionListener) */ public void addSelectionInterval(int index0, int index1) { setSelectionInterval(index0, index1); } /** * Clears the selection if it is equals to index0. Since this model * supports only a single selection index, the index1 can be ignored * for the selection.

* * If this represents a change to the current selection, then * notify each ListSelectionListener. Note that index0 doesn't have * to be less than or equal to index1. * * @param index0 one end of the interval. * @param index1 other end of the interval * @see #addListSelectionListener(ListSelectionListener) */ public void removeSelectionInterval(int index0, int index1) { if ((index0 == -1) || (index1 == -1)) { return; } int max = Math.max(index0, index1); int min = Math.min(index0, index1); if ((min <= getSelectionIndex()) && (getSelectionIndex() <= max)) { clearSelection(); } } /** * Returns the selection index. * * @return the selection index * @see #getMinSelectionIndex() * @see #setSelectionInterval(int, int) * @see #addSelectionInterval(int, int) */ public int getMinSelectionIndex() { return getSelectionIndex(); } /** * Returns the selection index. * * @return the selection index * @see #getMinSelectionIndex() * @see #setSelectionInterval(int, int) * @see #addSelectionInterval(int, int) */ public int getMaxSelectionIndex() { return getSelectionIndex(); } /** * Checks and answers if the given index is selected or not. * * @param index the index to be checked * @return true if selected, false if deselected * @see javax.swing.ListSelectionModel#isSelectedIndex(int) */ public boolean isSelectedIndex(int index) { return index < 0 ? false : index == getSelectionIndex(); } /** * Returns the selection index. * * @return the selection index * @see #getAnchorSelectionIndex() * @see #setSelectionInterval(int, int) * @see #addSelectionInterval(int, int) */ public int getAnchorSelectionIndex() { return getSelectionIndex(); } /** * Sets the selection index. * * @param newSelectionIndex the new selection index * @see #getLeadSelectionIndex() */ public void setAnchorSelectionIndex(int newSelectionIndex) { setSelectionIndex(newSelectionIndex); } /** * Returns the selection index. * * @return the selection index * @see #getAnchorSelectionIndex() * @see #setSelectionInterval(int, int) * @see #addSelectionInterval(int, int) */ public int getLeadSelectionIndex() { return getSelectionIndex(); } /** * Sets the selection index. * * @param newSelectionIndex the new selection index * @see #getLeadSelectionIndex() */ public void setLeadSelectionIndex(int newSelectionIndex) { setSelectionIndex(newSelectionIndex); } /** * Changes the selection to have no index selected. If this represents * a change to the current selection then notify each ListSelectionListener. * * @see #addListSelectionListener(ListSelectionListener) */ public void clearSelection() { setSelectionIndex(-1); } /** * Returns true if no index is selected. * * @return true if no index is selected */ public boolean isSelectionEmpty() { return getSelectionIndex() == -1; } /** * Inserts length indices beginning before/after index. If the value * This method is typically called to synchronize the selection model * with a corresponding change in the data model. * * @param index the index to start the insertion * @param length the length of the inserted interval * @param before true to insert before the start index */ public void insertIndexInterval(int index, int length, boolean before) { /* * The first new index will appear at insMinIndex and the last one will * appear at insMaxIndex */ if (isSelectionEmpty()) return; int insMinIndex = (before) ? index : index + 1; int selectionIndex = getSelectionIndex(); // If the added elements are after the index; do nothing. if (selectionIndex >= insMinIndex) { setSelectionIndex(selectionIndex + length); } } /** * Remove the indices in the interval index0,index1 (inclusive) from * the selection model. This is typically called to sync the selection * model width a corresponding change in the data model.

* * Clears the selection if it is in the specified interval. * * @param index0 the first index to remove from the selection * @param index1 the last index to remove from the selection * @throws IndexOutOfBoundsException if either index0 * or index1 are less than -1 * @see ListSelectionModel#removeSelectionInterval(int, int) * @see #setSelectionInterval(int, int) * @see #addSelectionInterval(int, int) * @see #addListSelectionListener(ListSelectionListener) */ public void removeIndexInterval(int index0, int index1) { if ((index0 < -1) || (index1 < -1)) { throw new IndexOutOfBoundsException( "Both indices must be greater or equals to -1."); } if (isSelectionEmpty()) return; int lower = Math.min(index0, index1); int upper = Math.max(index0, index1); int selectionIndex = getSelectionIndex(); if ((lower <= selectionIndex) && (selectionIndex <= upper)) { clearSelection(); } else if (upper < selectionIndex) { int translated = selectionIndex - (upper - lower + 1); setSelectionInterval(translated, translated); } } /** * This property is true if upcoming changes to the value * of the model should be considered a single event. For example * if the model is being updated in response to a user drag, * the value of the valueIsAdjusting property will be set to true * when the drag is initiated and be set to false when * the drag is finished. This property allows listeners to * to update only when a change has been finalized, rather * than always handling all of the intermediate values. * * @param newValueIsAdjusting The new value of the property. * @see #getValueIsAdjusting() */ public void setValueIsAdjusting(boolean newValueIsAdjusting) { boolean oldValueIsAdjusting = valueIsAdjusting; if (oldValueIsAdjusting == newValueIsAdjusting) { return; } valueIsAdjusting = newValueIsAdjusting; fireValueChanged(newValueIsAdjusting); } /** * Returns true if the value is undergoing a series of changes. * @return true if the value is currently adjusting * @see #setValueIsAdjusting(boolean) */ public boolean getValueIsAdjusting() { return valueIsAdjusting; } /** * Sets the selection mode. Only SINGLE_SELECTION is allowed * in this implementation. Other modes are not supported and will throw * an IllegalArgumentException.

* * With SINGLE_SELECTION only one list index * can be selected at a time. In this mode the setSelectionInterval * and addSelectionInterval methods are equivalent, and only * the second index argument (the "lead index") is used. * * @param selectionMode the mode to be set * @see #getSelectionMode() * @see javax.swing.ListSelectionModel#setSelectionMode(int) */ public void setSelectionMode(int selectionMode) { if (selectionMode != SINGLE_SELECTION) { throw new UnsupportedOperationException( "The SingleListSelectionAdapter must be used in single selection mode."); } } /** * Returns the fixed selection mode SINGLE_SELECTION. * * @return SINGLE_SELECTION * @see #setSelectionMode(int) */ public int getSelectionMode() { return ListSelectionModel.SINGLE_SELECTION; } /** * Add a listener to the list that's notified each time a change * to the selection occurs. * * @param listener the ListSelectionListener * @see #removeListSelectionListener(ListSelectionListener) * @see #setSelectionInterval(int, int) * @see #addSelectionInterval(int, int) * @see #removeSelectionInterval(int, int) * @see #clearSelection() * @see #insertIndexInterval(int, int, boolean) * @see #removeIndexInterval(int, int) */ public void addListSelectionListener(ListSelectionListener listener) { listenerList.add(ListSelectionListener.class, listener); } /** * Remove a listener from the list that's notified each time a * change to the selection occurs. * * @param listener the ListSelectionListener * @see #addListSelectionListener(ListSelectionListener) */ public void removeListSelectionListener(ListSelectionListener listener) { listenerList.remove(ListSelectionListener.class, listener); } /** * Returns an array of all the list selection listeners * registered on this DefaultListSelectionModel. * * @return all of this model's ListSelectionListeners * or an empty * array if no list selection listeners are currently registered * * @see #addListSelectionListener(ListSelectionListener) * @see #removeListSelectionListener(ListSelectionListener) */ public ListSelectionListener[] getListSelectionListeners() { return listenerList.getListeners(ListSelectionListener.class); } // Change Handling and Listener Notification **************************** // Updates first and last change indices private void markAsDirty(int index) { if (index < 0) return; firstAdjustedIndex = Math.min(firstAdjustedIndex, index); lastAdjustedIndex = Math.max(lastAdjustedIndex, index); } /** * Notifies listeners that we have ended a series of adjustments. * * @param isAdjusting true if there are multiple changes */ private void fireValueChanged(boolean isAdjusting) { if (lastChangedIndex == MIN) { return; } /* Change the values before sending the event to the * listeners in case the event causes a listener to make * another change to the selection. */ int oldFirstChangedIndex = firstChangedIndex; int oldLastChangedIndex = lastChangedIndex; firstChangedIndex = MAX; lastChangedIndex = MIN; fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex, isAdjusting); } /** * Notifies ListSelectionListeners that the value * of the selection, in the closed interval firstIndex, * lastIndex, has changed. * * @param firstIndex the first index that has changed * @param lastIndex the last index that has changed */ private void fireValueChanged(int firstIndex, int lastIndex) { fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting()); } /** * Notifies all registered listeners that the selection has changed. * * @param firstIndex the first index in the interval * @param lastIndex the last index in the interval * @param isAdjusting true if this is the final change in a series of * adjustments * @see EventListenerList */ private void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting) { Object[] listeners = listenerList.getListenerList(); ListSelectionEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListSelectionListener.class) { if (e == null) { e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting); } ((ListSelectionListener) listeners[i + 1]).valueChanged(e); } } } private void fireValueChanged() { if (lastAdjustedIndex == MIN) { return; } /* If getValueAdjusting() is true, (eg. during a drag opereration) * record the bounds of the changes so that, when the drag finishes (and * setValueAdjusting(false) is called) we can post a single event * with bounds covering all of these individual adjustments. */ if (getValueIsAdjusting()) { firstChangedIndex = Math.min(firstChangedIndex, firstAdjustedIndex); lastChangedIndex = Math.max(lastChangedIndex, lastAdjustedIndex); } /* Change the values before sending the event to the * listeners in case the event causes a listener to make * another change to the selection. */ int oldFirstAdjustedIndex = firstAdjustedIndex; int oldLastAdjustedIndex = lastAdjustedIndex; firstAdjustedIndex = MAX; lastAdjustedIndex = MIN; fireValueChanged(oldFirstAdjustedIndex, oldLastAdjustedIndex); } // Helper Classes ******************************************************* /** * Listens to changes of the selection index. */ private final class SelectionIndexChangeHandler implements PropertyChangeListener { /** * The selection index has been changed. * Notifies all registered listeners about the change. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { Object oldValue = evt.getOldValue(); Object newValue = evt.getNewValue(); int oldIndex = (oldValue == null) ? MIN : ((Integer) oldValue).intValue(); int newIndex = (newValue == null) ? MIN : ((Integer) newValue).intValue(); setSelectionIndex(oldIndex, newIndex); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/package.html0000644000175000017500000000513211374522114026277 0ustar twernertwerner Contains adapters that convert {@link com.jgoodies.binding.value.ValueModel}s to Swing model interfaces. These adapters are intended to replace the Swing model convenience implementations.

Related Documentation

For more information see: @see com.jgoodies.binding @see com.jgoodies.binding.beans @see com.jgoodies.binding.formatter @see com.jgoodies.binding.list @see com.jgoodies.binding.value jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/ColorSelectionAdapter.java0000644000175000017500000001464111374522114031113 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.awt.Color; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.colorchooser.DefaultColorSelectionModel; import com.jgoodies.binding.value.ValueModel; /** * Converts ValueModels to the ColorSelectionModel interface. Useful to bind * JColorChooser and similar classes to a ValueModel.

* * Constraints: The subject ValueModel must be of type Color * and must allow read-access to its value. Also, it is strongly recommended * (though not required) that the underlying ValueModel provides only non-null * values. This is so because the ColorSelectionModel behavior is undefined * for {@code null} values and it may have unpredictable results.

* * Examples:

 * // Recommended binding style using a factory
 * ValueModel model = presentationModel.getModel(MyBean.PROPERTYNAME_COLOR);
 * JColorChooser colorChooser = BasicComponentFactory.createColorChooser(model);
 *
 * // Binding using the Bindings class
 * ValueModel model = presentationModel.getModel(MyBean.PROPERTYNAME_COLOR);
 * JColorChooser colorChooser = new JColorChooser();
 * Bindings.bind(colorChooser, model);
 *
 * // Hand-made binding
 * ValueModel model = presentationModel.getModel(MyBean.PROPERTYNAME_COLOR);
 * JColorChooser colorChooser = new JColorChooser(new ColorSelectionAdapter(model));
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @see javax.swing.colorchooser.ColorSelectionModel * @see javax.swing.JColorChooser * * @since 1.0.3 */ public final class ColorSelectionAdapter extends DefaultColorSelectionModel { /** * Refers to the underlying ValueModel that is used to read and write values. */ private final ValueModel subject; /** * An optional color that is returned as selected color * if the underlying ValueModel returns {@code null}. */ private final Color defaultColor; // Instance Creation ***************************************************** /** * Constructs a ColorSelectionAdapter on the given subject ValueModel. * * @param subject the subject that holds the value * @throws NullPointerException if the subject is {@code null}. */ public ColorSelectionAdapter(ValueModel subject) { this(subject, null); } /** * Constructs a ColorSelectionAdapter on the given subject ValueModel. * * @param subject the subject that holds the value * @param defaultColor an optional default color that is used as * selected color if the subject returns {@code null} * @throws NullPointerException if {@code subject} is {@code null} */ public ColorSelectionAdapter(ValueModel subject, Color defaultColor) { this.subject = checkNotNull(subject, "The subject must not be null."); this.defaultColor = defaultColor; subject.addValueChangeListener(new SubjectValueChangeHandler()); } // Overriding Superclass Behavior ***************************************** /** * Returns the selected Color which should be non-{@code null}. * The return value is the subject value model's value, if non-null, * otherwise the default color. Note that the latter may be null too. * * @return the selected Color * * @throws ClassCastException if the subject value is not a Color * * @see #setSelectedColor(Color) */ @Override public Color getSelectedColor() { Color subjectColor = (Color) subject.getValue(); return subjectColor != null ? subjectColor : defaultColor; } /** * Sets the selected color to color. * Note that setting the color to {@code null} * is undefined and may have unpredictable results. * This method fires a state changed event if it sets the * current color to a new non-{@code null} color. * * @param color the new Color * * @see #getSelectedColor() */ @Override public void setSelectedColor(Color color) { subject.setValue(color); } // Event Handling ********************************************************* /** * Handles changes in the subject's value. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { /** * The subject value has changed. Notifies all registered listeners * about a state change. * * @param evt the property change event fired by the subject */ public void propertyChange(PropertyChangeEvent evt) { fireStateChanged(); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/BoundedRangeAdapter.java0000644000175000017500000003527411374522114030531 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Serializable; import javax.swing.BoundedRangeModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import com.jgoodies.binding.value.ValueModel; /** * Converts a ValueModel to the BoundedRangeModel interface. * Honors an upper and lower bound and returns the adapter's minimum * in case the subject value is {@code null}.

* * Example:

 * int minSaturation = 0;
 * int maxSaturation = 255;
 * PresentationModel pm = new PresentationModel(settings);
 * ValueModel saturationModel = pm.getModel("saturation");
 * JSlider saturationSlider = new JSlider(
 *     new BoundedRangeAdapter(saturationModel,
 *                             0,
 *                             minSaturation,
 *                             maxSaturation));
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.10 $ * * @see javax.swing.JSlider */ public final class BoundedRangeAdapter implements BoundedRangeModel, Serializable { /** * Only one ChangeEvent is needed per model instance since the * event's only (read-only) state is the source property. The source * of events generated here is always "this". */ private transient ChangeEvent changeEvent = null; /** * The listeners observing model changes. */ private final EventListenerList listenerList = new EventListenerList(); private final ValueModel subject; private int theExtent = 0; private int min = 0; private int max = 100; private boolean isAdjusting = false; // Instance Creation *************************************************** /** * Constructs a BoundedRangeAdapter on the given subject * using the specified extend, minimum and maximum values. * @param subject the underlying ValueModel that provides the value * @param extent the extent to be set * @param min the minimum to be set * @param max the maximum to be set * @throws IllegalArgumentException if the following constraints aren't * satisfied: min <= initial subject value <= value+extent <= max */ public BoundedRangeAdapter( ValueModel subject, int extent, int min, int max) { this.subject = subject; Object subjectValue = subject.getValue(); int initialValue = subjectValue == null ? min : ((Integer) subjectValue).intValue(); initialize(initialValue, extent, min, max); subject.addValueChangeListener(new SubjectValueChangeHandler()); } // ************************************************************************ /** * Returns this model's extent. * * @return the model's extent * @see #setExtent(int) * @see BoundedRangeModel#getExtent() */ public int getExtent() { return theExtent; } /** * Returns this model's upper bound, the maximum. * * @return the model's maximum * @see #setMaximum(int) * @see BoundedRangeModel#getMaximum() */ public int getMaximum() { return max; } /** * Returns this model's lower bound, the minimum. * * @return the model's minimum * @see #setMinimum(int) * @see BoundedRangeModel#getMinimum() */ public int getMinimum() { return min; } /** * Returns the current subject value, or the minimum if * the subject value is {@code null}. * * @return the model's current value * @see #setValue(int) * @see BoundedRangeModel#getValue() */ public int getValue() { Object subjectValue = subject.getValue(); return subjectValue == null ? getMinimum() : ((Integer) subjectValue).intValue(); } /** * Returns true if the value is in the process of changing * as a result of actions being taken by the user. * * @return the value of the valueIsAdjusting property * @see #setValue(int) * @see BoundedRangeModel#getValueIsAdjusting() */ public boolean getValueIsAdjusting() { return isAdjusting; } /** * Sets the extent to n. Ensures that n * is greater than or equal to zero and falls within the adapter's * constraints: *
     *     minimum <= value <= value+extent <= maximum
     * 
* * @param n the new extent before ensuring a non-negative number * @see BoundedRangeModel#setExtent(int) */ public void setExtent(int n) { int newExtent = Math.max(0, n); int value = getValue(); if (value + newExtent > max) { newExtent = max - value; } setRangeProperties(value, newExtent, min, max, isAdjusting); } /** * Sets the maximum to n. Ensures that n * and the other three properties obey this adapter's constraints: *
     *     minimum <= value <= value+extent <= maximum
     * 
* * @param n the new maximum before ensuring this adapter's constraints * @see BoundedRangeModel#setMaximum(int) */ public void setMaximum(int n) { int newMin = Math.min(n, min); int newValue = Math.min(n, getValue()); int newExtent = Math.min(n - newValue, theExtent); setRangeProperties(newValue, newExtent, newMin, n, isAdjusting); } /** * Sets the minimum to n. Ensures that n * and the other three properties obey this adapter's constraints: *
     *     minimum <= value <= value+extent <= maximum
     * 
* * @param n the new minimum before ensuring constraints * @see #getMinimum() * @see BoundedRangeModel#setMinimum(int) */ public void setMinimum(int n) { int newMax = Math.max(n, max); int newValue = Math.max(n, getValue()); int newExtent = Math.min(newMax - newValue, theExtent); setRangeProperties(newValue, newExtent, n, newMax, isAdjusting); } /** * Sets all of the BoundedRangeModel properties after forcing * the arguments to obey the usual constraints: *
     *     minimum <= value <= value+extent <= maximum
     * 

* * At most, one ChangeEvent is generated. * * @param newValue the value to be set * @param newExtent the extent to be set * @param newMin the minimum to be set * @param newMax the maximum to be set * @param adjusting true if there are other pending changes * @see BoundedRangeModel#setRangeProperties(int, int, int, int, boolean) * @see #setValue(int) * @see #setExtent(int) * @see #setMinimum(int) * @see #setMaximum(int) * @see #setValueIsAdjusting(boolean) */ public void setRangeProperties( int newValue, int newExtent, int newMin, int newMax, boolean adjusting) { if (newMin > newMax) { newMin = newMax; } if (newValue > newMax) { newMax = newValue; } if (newValue < newMin) { newMin = newValue; } /* Convert the addends to long so that extent can be * Integer.MAX_VALUE without rolling over the sum. * A JCK test covers this, see bug 4097718. */ if (((long) newExtent + (long) newValue) > newMax) { newExtent = newMax - newValue; } if (newExtent < 0) { newExtent = 0; } boolean isChange = (newValue != getValue()) || (newExtent != theExtent) || (newMin != min) || (newMax != max) || (adjusting != isAdjusting); if (isChange) { setValue0(newValue); theExtent = newExtent; min = newMin; max = newMax; isAdjusting = adjusting; fireStateChanged(); } } /** * Sets the current value of the model. For a slider, that * determines where the knob appears. Ensures that the new * value, n falls within the model's constraints: *

     *     minimum <= value <= value+extent <= maximum
     * 
* * @param n the new value before ensuring constraints * @see BoundedRangeModel#setValue(int) */ public void setValue(int n) { int newValue = Math.max(n, min); if (newValue + theExtent > max) { newValue = max - theExtent; } setRangeProperties(newValue, theExtent, min, max, isAdjusting); } /** * Sets the valueIsAdjusting property. * * @param b the new value * @see #getValueIsAdjusting() * @see #setValue(int) * @see BoundedRangeModel#setValueIsAdjusting(boolean) */ public void setValueIsAdjusting(boolean b) { setRangeProperties(getValue(), theExtent, min, max, b); } // Listeners and Events *************************************************** /** * Adds a ChangeListener. The change listeners are run each * time any one of the Bounded Range model properties changes. * * @param l the ChangeListener to add * @see #removeChangeListener(ChangeListener) * @see BoundedRangeModel#addChangeListener(ChangeListener) */ public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } /** * Removes a ChangeListener. * * @param l the ChangeListener to remove * @see #addChangeListener(ChangeListener) * @see BoundedRangeModel#removeChangeListener(ChangeListener) */ public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } /** * Runs each ChangeListeners stateChanged() method. * * @see #setRangeProperties(int, int, int, int, boolean) * @see EventListenerList */ protected void fireStateChanged() { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { if (changeEvent == null) { changeEvent = new ChangeEvent(this); } ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent); } } } // Misc ******************************************************************* /** * Initializes value, extent, minimum and maximum. Adjusting is false. * * @param initialValue the initialValue * @param extent the extent to be set * @param minimum the minimum to be set * @param maximum the maximum to be set * * @throws IllegalArgumentException if the following constraints * aren't satisfied: min <= value <= value+extent <= max */ private void initialize(int initialValue, int extent, int minimum, int maximum) { if ((maximum >= minimum) && (initialValue >= minimum) && ((initialValue + extent) >= initialValue) && ((initialValue + extent) <= maximum)) { this.theExtent = extent; this.min = minimum; this.max = maximum; } else { throw new IllegalArgumentException("invalid range properties"); } } private void setValue0(int newValue) { subject.setValue(Integer.valueOf(newValue)); } /** * Returns a string that displays all of the BoundedRangeModel properties. * * @return a string representation of the properties */ @Override public String toString() { String modelString = "value=" + getValue() + ", " + "extent=" + getExtent() + ", " + "min=" + getMinimum() + ", " + "max=" + getMaximum() + ", " + "adj=" + getValueIsAdjusting(); return getClass().getName() + "[" + modelString + "]"; } /** * Handles changes in the subject's value. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { /** * The subect's value has changed. Fires a state change so * all registered listeners will be notified about the change. * * @param evt the property change event fired by the subject */ public void propertyChange(PropertyChangeEvent evt) { fireStateChanged(); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/ToggleButtonAdapter.java0000644000175000017500000001731311374522114030603 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JToggleButton; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * Converts ValueModels to the ToggleButtonModel interface. Useful to bind * JToggleButton, JCheckBox and JCheckBoxMenuItem to a ValueModel.

* * This adapter holds two values that represent the selected and the deselected * state. These are used to determine the selection state if the underlying * subject ValueModel changes its value. If the selection is set, the * corresponding representant is written to the underlying ValueModel.

* * Constraints: The subject ValueModel must allow * read-access to its value. Also, it is strongly recommended (though not * required) that the underlying ValueModel provides only two values, for * example Boolean.TRUE and Boolean.FALSE. This is so because the toggle button * component may behave "strangely" when it is used with ValueModels that * provide more than two elements.

* * Examples:

 * // Recommended binding style using a factory
 * ValueModel model = presentationModel.getModel(MyBean.PROPERTYNAME_VISIBLE);
 * JCheckBox visibleBox = BasicComponentFactory.createCheckBox(model, "Visible");
 *
 * // Binding using the Bindings class
 * ValueModel model = presentationModel.getModel(MyBean.PROPERTYNAME_VISIBLE);
 * JCheckBox visibleBox = new JCheckBox("Visible");
 * Bindings.bind(visibleBox, model);
 *
 * // Hand-made binding
 * ValueModel model = presentationModel.getModel(MyBean.PROPERTYNAME_VISIBLE);
 * JCheckBox visibleBox = new JCheckBox("Visible");
 * visibleBox.setModel(new ToggleButtonAdapter(model));
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @see javax.swing.ButtonModel * @see javax.swing.JCheckBox * @see javax.swing.JCheckBoxMenuItem */ public final class ToggleButtonAdapter extends JToggleButton.ToggleButtonModel { /** * Refers to the underlying ValueModel that is used to read and write values. */ private final ValueModel subject; /** * The value that represents the selected state. */ private final Object selectedValue; /** * The value that represents the deselected state. */ private final Object deselectedValue; // Instance Creation ***************************************************** /** * Constructs a ToggleButtonAdapter on the given subject ValueModel. * The created adapter will be selected if and only if the * subject's initial value is Boolean.TRUE. * * @param subject the subject that holds the value * @throws NullPointerException if the subject is {@code null}. */ public ToggleButtonAdapter(ValueModel subject) { this(subject, Boolean.TRUE, Boolean.FALSE); } /** * Constructs a ToggleButtonAdapter on the given subject ValueModel * using the specified values for the selected and deselected state. * The created adapter will be selected if and only if the * subject's initial value equals the given selectedValue. * * @param subject the subject that holds the value * @param selectedValue the value that will be set if this is selected * @param deselectedValue the value that will be set if this is deselected * * @throws NullPointerException if the subject is {@code null}. * @throws IllegalArgumentException if the selected and deselected values * are equal */ public ToggleButtonAdapter( ValueModel subject, Object selectedValue, Object deselectedValue) { this.subject = checkNotNull(subject, "The subject must not be null."); checkArgument(!Objects.equals(selectedValue, deselectedValue), "The selected value must not equal the deselected value."); this.selectedValue = selectedValue; this.deselectedValue = deselectedValue; subject.addValueChangeListener(new SubjectValueChangeHandler()); updateSelectedState(); } // ToggleButtonModel Implementation *********************************** /** * First, the subject value is set to this adapter's selected value if * the argument is {@code true}, to the deselected value otherwise. * Second, this adapter's state is set to the then current subject value. * This ensures that the selected state is synchronized with the subject * - even if the subject rejects the change. * * @param b {@code true} sets the selected value as subject value, * {@code false} sets the deselected value as subject value */ @Override public void setSelected(boolean b) { subject.setValue(b ? selectedValue : deselectedValue); updateSelectedState(); } /** * Updates this adapter's selected state to reflect * whether the subject holds the selected value or not. * Does not modify the subject value. */ private void updateSelectedState() { boolean subjectHoldsChoiceValue = Objects.equals(selectedValue, subject.getValue()); super.setSelected(subjectHoldsChoiceValue); } // Event Handling ********************************************************* /** * Handles changes in the subject's value. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { /** * The subject value has changed. Updates this adapter's selected * state to reflect whether the subject holds the selected value or not. * * @param evt the property change event fired by the subject */ public void propertyChange(PropertyChangeEvent evt) { updateSelectedState(); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/ComboBoxAdapter.java0000644000175000017500000004027311374522114027677 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; import java.util.List; import javax.swing.AbstractListModel; import javax.swing.ComboBoxModel; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import com.jgoodies.binding.list.SelectionInList; import com.jgoodies.binding.value.ValueModel; /** * A {@link ComboBoxModel} implementation that holds the choice list and a * selection. This adapter has two modes that differ primarily in how * the selection is kept synchronized with the combo's list. * 1) If you construct a ComboBoxAdapter with a {@link SelectionInList}, * the selection will be guaranteed to be in the list, and the selection * will reflect changes in the list. * 2) If you construct this adapter with a separate selection holder, * the selection won't be affected by any change in the combo's list.

* * In both cases, the combo's list of element will reflect changes in the list, * if it's a ListModel and will ignore content changes, if it's a List.

* * Example:

 * String[] countries = new String[] { "USA", "Germany", "France", ... };
 *
 * // Using an array and ValueModel
 * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
 * ComboBoxAdapter adapter = new ComboBoxAdapter(countries, contryModel);
 * JComboBox countryBox    = new JComboBox(adapter);
 *
 * // Using a List and ValueModel
 * List countryList = Arrays.asList(countries);
 * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
 * ComboBoxAdapter adapter = new ComboBoxAdapter(countryList, contryModel);
 * JComboBox countryBox    = new JComboBox(adapter);
 *
 * // Using a ListModel and ValueModel
 * ListModel countryListModel = new ArrayListModel(Arrays.asList(countries));
 * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
 * ComboBoxAdapter adapter = new ComboBoxAdapter(countryListModel, contryModel);
 * JComboBox countryBox    = new JComboBox(adapter);
 *
 * // Using a SelectionInList - allows only selection of contained elements
 * ListModel countryListModel = new ArrayListModel(Arrays.asList(countries));
 * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
 * SelectionInList sil     = new SelectionInList(countryListModel, countryModel);
 * ComboBoxAdapter adapter = new ComboBoxAdapter(sil);
 * JComboBox countryBox    = new JComboBox(adapter);
 *
 * // Using ValueModels for the list holder and the selection holder
 * class Country extends Model {
 *     ListModel getLocales();
 *     Locale getDefaultLocale();
 *     void setDefaultLocale(Locale locale);
 * }
 *
 * BeanAdapter beanAdapter = new BeanAdapter(null, true);
 * ValueModel localesHolder      = beanAdapter.getValueModel("locales");
 * ValueModel defaultLocaleModel = beanAdapter.getValueModel("defaultLocale");
 * ComboBoxAdapter adapter = new ComboBoxAdapter(
 *         localesHolder, defaultLocaleModel);
 * JComboBox localeBox = new JComboBox(adapter);
 *
 * beanAdapter.setBean(myCountry);
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.15 $ * * @see javax.swing.JComboBox * * @param the type of the combo box items */ public final class ComboBoxAdapter extends AbstractListModel implements ComboBoxModel { /** * Holds the list of choices. */ private final ListModel listModel; /** * Refers to the ValueModel that holds the current selection. * In case this adapter is constructed for a SelectionInList * or SelectionInListModel, the selection holder will be updated * if the SIL or SILModel changes its selection holder. */ private ValueModel selectionHolder; /** * Holds the listener that handles selection changes. */ private final PropertyChangeListener selectionChangeHandler; // Instance creation ****************************************************** /** * Constructs a ComboBoxAdapter for the specified List of items * and the given selection holder. Structural changes in the list * will be ignored.

* * Example:

     * String[] countries = new String[] { "USA", "Germany", "France", ... };
     * List countryList = Arrays.asList(countries);
     * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
     * ComboBoxAdapter adapter = new ComboBoxAdapter(countryList, contryModel);
     * JComboBox countryBox    = new JComboBox(adapter);
     * 
* * @param items the list of items * @param selectionHolder holds the selection of the combo * @throws NullPointerException if the list of items or the selection holder * is {@code null} */ public ComboBoxAdapter(List items, ValueModel selectionHolder) { this(new ListModelAdapter(items), selectionHolder); checkNotNull(items, "The list must not be null."); } /** * Constructs a ComboBoxAdapter for the given ListModel and selection * holder. Structural changes in the ListModel will be reflected by * this adapter, but won't affect the selection.

* * Example:

     * String[] countries = new String[] { "USA", "Germany", "France", ... };
     * ListModel countryList = new ArrayListModel(Arrays.asList(countries));
     * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
     * ComboBoxAdapter adapter = new ComboBoxAdapter(countryList, contryModel);
     * JComboBox countryBox    = new JComboBox(adapter);
     * 
* * @param listModel the initial list model * @param selectionHolder holds the selection of the combo * @throws NullPointerException if the list of items or the selection holder * is {@code null} */ public ComboBoxAdapter(ListModel listModel, ValueModel selectionHolder) { this.listModel = checkNotNull(listModel, "The ListModel must not be null."); this.selectionHolder = checkNotNull(selectionHolder, "The selection holder must not be null."); listModel.addListDataListener(new ListDataChangeHandler()); selectionChangeHandler = new SelectionChangeHandler(); setSelectionHolder(selectionHolder); } /** * Constructs a ComboBoxAdapter for the specified List of items and the * given selection holder. Structural changes in the list will be ignored. *

* * Example:

     * String[] countries = new String[] { "USA", "Germany", "France", ... };
     * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
     * ComboBoxAdapter adapter = new ComboBoxAdapter(countries, contryModel);
     * JComboBox countryBox    = new JComboBox(adapter);
     * 
* * @param items the list of items * @param selectionHolder holds the selection of the combo * @throws NullPointerException if the list of items or the selection holder * is {@code null} */ public ComboBoxAdapter(E[] items, ValueModel selectionHolder) { this(new ListModelAdapter(items), selectionHolder); } /** * Constructs a ComboBoxAdapter for the given SelectionInList. Note that * selections which are not elements of the list will be rejected.

* * Example:

     * String[] countries = new String[] { "USA", "Germany", "France", ... };
     * List countryList = Arrays.asList(countries);
     * ValueModel countryModel = new PropertyAdapter(customer, "country", true);
     * SelectionInList sil     = new SelectionInList(countryList, countryModel);
     * ComboBoxAdapter adapter = new ComboBoxAdapter(sil);
     * JComboBox countryBox    = new JComboBox(adapter);
     * 
* * @param selectionInList provides the list and selection * @throws NullPointerException if the selectionInList is * {@code null} */ public ComboBoxAdapter(SelectionInList selectionInList) { this(selectionInList, selectionInList); selectionInList.addPropertyChangeListener( SelectionInList.PROPERTYNAME_SELECTION_HOLDER, new SelectionHolderChangeHandler()); } // ComboBoxModel API **************************************************** /** * Returns the selected item by requesting the current value from the * either the selection holder or the SelectionInList's selection. * * @return The selected item or {@code null} if there is no selection */ public E getSelectedItem() { return (E) selectionHolder.getValue(); } /** * Sets the selected item. The implementation of this method should notify * all registered ListDataListeners that the contents has * changed. * * @param object the list object to select or {@code null} to clear * the selection */ public void setSelectedItem(Object object) { selectionHolder.setValue(object); } /** * Returns the length of the item list. * * @return the length of the list */ public int getSize() { return listModel.getSize(); } /** * Returns the value at the specified index. * * @param index the requested index * @return the value at index */ public E getElementAt(int index) { return (E) listModel.getElementAt(index); } // Helper Code ************************************************************ private void setSelectionHolder(ValueModel newSelectionHolder) { ValueModel oldSelectionHolder = selectionHolder; if (oldSelectionHolder != null) { oldSelectionHolder.removeValueChangeListener(selectionChangeHandler); } selectionHolder = checkNotNull(newSelectionHolder, "The selection holder must not be null."); newSelectionHolder.addValueChangeListener(selectionChangeHandler); } // Event Handling ********************************************************* private void fireContentsChanged() { fireContentsChanged(this, -1, -1); } /** * Listens to selection changes and fires a contents change event. */ private final class SelectionChangeHandler implements PropertyChangeListener { /** * The selection has changed. Notifies all * registered listeners about the change. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { fireContentsChanged(); } } /** * Handles ListDataEvents in the list model. */ private final class ListDataChangeHandler implements ListDataListener { /** * Sent after the indices in the index0, index1 interval have been * inserted in the data model. The new interval includes both index0 and * index1. * * @param evt a ListDataEvent encapsulating the event * information */ public void intervalAdded(ListDataEvent evt) { fireIntervalAdded(ComboBoxAdapter.this, evt.getIndex0(), evt.getIndex1()); } /** * Sent after the indices in the index0, index1 interval have been * removed from the data model. The interval includes both index0 and * index1. * * @param evt a ListDataEvent encapsulating the event * information */ public void intervalRemoved(ListDataEvent evt) { fireIntervalRemoved(ComboBoxAdapter.this, evt.getIndex0(), evt.getIndex1()); } /** * Sent when the contents of the list has changed in a way that's too * complex to characterize with the previous methods. For example, this * is sent when an item has been replaced. Index0 and index1 bracket the * change. * * @param evt a ListDataEvent encapsulating the event * information */ public void contentsChanged(ListDataEvent evt) { fireContentsChanged(ComboBoxAdapter.this, evt.getIndex0(), evt.getIndex1()); } } /** * Listens to changes of the selection holder and updates our internal * reference to it. */ private final class SelectionHolderChangeHandler implements PropertyChangeListener { /** * The SelectionInList has changed the selection holder. * Update our internal reference to the new holder and * notify registered listeners about a selection change. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { setSelectionHolder((ValueModel) evt.getNewValue()); fireContentsChanged(); } } // Helper Classes ********************************************************* /** * Converts a List to ListModel by wrapping the underlying list. */ private static final class ListModelAdapter extends AbstractListModel { private final List aList; ListModelAdapter(List list) { this.aList = list; } ListModelAdapter(E[] elements) { this(Arrays.asList(elements)); } /** * Returns the length of the list. * @return the length of the list */ public int getSize() { return aList.size(); } /** * Returns the value at the specified index. * @param index the requested index * @return the value at index */ public E getElementAt(int index) { return aList.get(index); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/TextComponentConnector.java0000644000175000017500000004300011374522114031337 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import javax.swing.text.PlainDocument; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * Connects a String typed ValueModel and a JTextField or JTextArea. * At construction time the text component content is updated * with the subject's contents.

* * This connector has been designed for text components that display a plain * String. In case of a JEditorPane, the binding may require more information * then the plain String, for example styles. Since this is outside the scope * of this connector, the public constructors prevent a construction for * general JTextComponents. If you want to establish a one-way binding for * a display JEditorPane, use a custom listener instead.

* * This class provides limited support for handling * subject value modifications while updating the subject. * If a Document change initiates a subject value update, the subject * will be observed and a property change fired by the subject will be * handled - if any. In most cases, the subject will notify about a * change to the text that was just set by this connector. * However, in some cases the subject may decide to modify this text, * for example to ensure upper case characters. * Since at this moment, this adapter's Document is still write-locked, * the Document update is performed later using * SwingUtilities#invokeLater.

* * Note: * Such an update will typically change the Caret position in JTextField's * and other JTextComponent's that are synchronized using this class. * Hence, the subject value modifications can be used with * commit-on-focus-lost text components, but typically not with a * commit-on-key-typed component. For the latter case, you may consider * using a custom DocumentFilter.

* * Constraints: * The ValueModel must be of type String.

* * Examples:

 * ValueModel lastNameModel = new PropertyAdapter(customer, "lastName", true);
 * JTextField lastNameField = new JTextField();
 * TextComponentConnector.connect(lastNameModel, lastNameField);
 *
 * ValueModel codeModel = new PropertyAdapter(shipment, "code", true);
 * JTextField codeField = new JTextField();
 * TextComponentConnector connector =
 *     new TextComponentConnector(codeModel, codeField);
 * connector.updateTextComponent();
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.15 $ * * @see ValueModel * @see Document * @see PlainDocument * * @since 1.2 */ public final class TextComponentConnector { /** * Holds the underlying ValueModel that is used to read values, * to update the document and to write values if the document changes. */ private final ValueModel subject; /** * Refers to the text component that shall be synchronized * with the subject. */ private final JTextComponent textComponent; /** * Holds the text component's current document. * Used for the rare case where the text component fires * a PropertyChangeEvent for the "document" property * with oldValue or newValue == {@code null}. */ private Document document; private final SubjectValueChangeHandler subjectValueChangeHandler; private final DocumentListener textChangeHandler; private final PropertyChangeListener documentChangeHandler; // Instance Creation ****************************************************** /** * Constructs a TextComponentConnector that connects the specified * String-typed subject ValueModel with the given text area.

* * In case you don't need the TextComponentConnector instance, you better * use one of the static #connect methods. * This constructor may confuse developers, if you just use * the side effects performed in the constructor; this is because it is * quite unconventional to instantiate an object that you never use. * * @param subject the underlying String typed ValueModel * @param textArea the JTextArea to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text area is {@code null} */ public TextComponentConnector(ValueModel subject, JTextArea textArea) { this(subject, (JTextComponent) textArea); } /** * Constructs a TextComponentConnector that connects the specified * String-typed subject ValueModel with the given text field.

* * In case you don't need the TextComponentConnector instance, you better * use one of the static #connect methods. * This constructor may confuse developers, if you just use * the side effects performed in the constructor; this is because it is * quite unconventional to instantiate an object that you never use. * * @param subject the underlying String typed ValueModel * @param textField the JTextField to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text field is {@code null} */ public TextComponentConnector(ValueModel subject, JTextField textField) { this(subject, (JTextComponent) textField); } /** * Constructs a TextComponentConnector that connects the specified * String-typed subject ValueModel with the given JTextComponent.

* * In case you don't need the TextComponentConnector instance, you better * use one of the static #connect methods. * This constructor may confuse developers, if you just use * the side effects performed in the constructor; this is because it is * quite unconventional to instantiate an object that you never use. * * @param subject the underlying String typed ValueModel * @param textComponent the JTextComponent to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text component is {@code null} */ private TextComponentConnector(ValueModel subject, JTextComponent textComponent) { this.subject = checkNotNull(subject, "The subject must not be null."); this.textComponent = checkNotNull(textComponent, "The text component must not be null."); this.subjectValueChangeHandler = new SubjectValueChangeHandler(); this.textChangeHandler = new TextChangeHandler(); document = textComponent.getDocument(); reregisterTextChangeHandler(null, document); subject.addValueChangeListener(subjectValueChangeHandler); documentChangeHandler = new DocumentChangeHandler(); textComponent.addPropertyChangeListener("document", documentChangeHandler); } /** * Establishes a synchronization between the specified String-typed * subject ValueModel and the given text area. Does not synchronize now. * * @param subject the underlying String typed ValueModel * @param textArea the JTextArea to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text area is {@code null} */ public static void connect(ValueModel subject, JTextArea textArea) { new TextComponentConnector(subject, textArea); } /** * Establishes a synchronization between the specified String-typed * subject ValueModel and the given text field. Does not synchronize now. * * @param subject the underlying String typed ValueModel * @param textField the JTextField to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text area is {@code null} */ public static void connect(ValueModel subject, JTextField textField) { new TextComponentConnector(subject, textField); } // Synchronization ******************************************************** /** * Reads the current text from the document * and sets it as new value of the subject. */ public void updateSubject() { setSubjectText(getDocumentText()); } public void updateTextComponent() { setDocumentTextSilently(getSubjectText()); } /** * Returns the text contained in the document. * * @return the text contained in the document */ private String getDocumentText() { return textComponent.getText(); } /** * Sets the text component's contents without notifying the subject * about the change. Invoked by the subject change listener. * Sets the text, then sets the caret position to 0. * * @param newText the text to be set in the document */ private void setDocumentTextSilently(String newText) { textComponent.getDocument().removeDocumentListener(textChangeHandler); textComponent.setText(newText); textComponent.setCaretPosition(0); textComponent.getDocument().addDocumentListener(textChangeHandler); } /** * Returns the subject's text value. * * @return the subject's text value * @throws ClassCastException if the subject value is not a String */ private String getSubjectText() { String str = (String) subject.getValue(); return str == null ? "" : str; } /** * Sets the given text as new subject value. Since the subject may modify * this text, we cannot update silently, i.e. we cannot remove and add * the subjectValueChangeHandler before/after the update. Since this * change is invoked during a Document write operation, the document * is write-locked and so, we cannot modify the document before all * document listeners have been notified about the change.

* * Therefore we listen to subject changes and defer any document changes * using SwingUtilities.invokeLater. This mode is activated * by setting the subject change handler's updateLater to true. * * @param newText the text to be set in the subject */ private void setSubjectText(String newText) { subjectValueChangeHandler.setUpdateLater(true); try { subject.setValue(newText); } finally { subjectValueChangeHandler.setUpdateLater(false); } } private void reregisterTextChangeHandler(Document oldDocument, Document newDocument) { if (oldDocument != null) { oldDocument.removeDocumentListener(textChangeHandler); } if (newDocument != null) { newDocument.addDocumentListener(textChangeHandler); } } // Misc ******************************************************************* /** * Removes the internal listeners from the subject, text component, * and text component's document. * This connector must not be used after calling #release.

* * To avoid memory leaks it is recommended to invoke this method, * if the ValueModel lives much longer than the text component. * Instead of releasing a text connector, you typically make the ValueModel * obsolete by releasing the PresentationModel or BeanAdapter that has * created the ValueModel.

* * As an alternative you may use ValueModels that in turn use * event listener lists implemented using WeakReference. * * @see PresentationModel#release() * @see BeanAdapter#release() * @see java.lang.ref.WeakReference */ public void release() { reregisterTextChangeHandler(document, null); subject.removeValueChangeListener(subjectValueChangeHandler); textComponent.removePropertyChangeListener("document", documentChangeHandler); } // DocumentListener ******************************************************* /** * Updates the subject if the text has changed. */ private final class TextChangeHandler implements DocumentListener { /** * There was an insert into the document; update the subject. * * @param e the document event */ public void insertUpdate(DocumentEvent e) { updateSubject(); } /** * A portion of the document has been removed; update the subject. * * @param e the document event */ public void removeUpdate(DocumentEvent e) { updateSubject(); } /** * An attribute or set of attributes has changed; do nothing. * * @param e the document event */ public void changedUpdate(DocumentEvent e) { // Do nothing on attribute changes. } } /** * Handles changes in the subject value and updates this document * - if necessary.

* * Document changes update the subject text and result in a subject * property change. Most of these changes will just reflect the * former subject change. However, in some cases the subject may * modify the text set, for example to ensure upper case characters. * This method reduces the number of document updates by checking * the old and new text. If the old and new text are equal or * both null, this method does nothing.

* * Since subject changes as a result of a document change may not * modify the write-locked document immediately, we defer the update * if necessary using SwingUtilities.invokeLater.

* * See the TextComponentConnector's JavaDoc class comment * for the limitations of the deferred document change. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { private boolean updateLater; void setUpdateLater(boolean updateLater) { this.updateLater = updateLater; } /** * The subject value has changed; updates the document immediately * or later - depending on the updateLater state. * * @param evt the event to handle */ public void propertyChange(PropertyChangeEvent evt) { final String oldText = getDocumentText(); final Object newValue = evt.getNewValue(); final String newText = newValue == null ? getSubjectText() : (String) newValue; if (Objects.equals(oldText, newText)) { return; } if (updateLater) { SwingUtilities.invokeLater(new Runnable() { public void run() { setDocumentTextSilently(newText); } }); } else { setDocumentTextSilently(newText); } } } /** * Re-registers the text change handler after document changes. */ private final class DocumentChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { Document oldDocument = document; Document newDocument = textComponent.getDocument(); reregisterTextChangeHandler(oldDocument, newDocument); document = newDocument; } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/adapter/PreferencesAdapter.java0000644000175000017500000003027111374522114030425 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.adapter; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.util.prefs.Preferences; import com.jgoodies.binding.value.AbstractValueModel; /** * A ValueModel implementation that reads and writes values from/to a key * of a given Preferences node under a specified key. * Write changes fire value changes.

* * Example:

 * String  prefsKey = "isShowing";
 * Boolean defaultValue = Boolean.TRUE;
 * Preferences prefs = Workbench.userPreferences();
 * ValueModel model = new PreferencesAdapter(prefs, prefsKey, defaultValue);
 * JCheckBox showingBox = new JCheckBox("Show tips");
 * showingBox.setModel(new ToggleButtonAdapter(model));
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.10 $ * * @see java.util.prefs.Preferences */ public final class PreferencesAdapter extends AbstractValueModel { private static final String ERROR_MSG = "Value must be a Boolean, Double, Float, Integer, Long, or String."; /** * Refers to the preferences node that is used to persist the bound data. */ private final Preferences prefs; /** * Holds the preferences key that is used to access the stored value. */ private final String key; /** * Refers to the type of accepted values. */ private final Class type; /** * Holds the default value that is used if the preferences * do not yet store a value. */ private final Object defaultValue; //Instance Creation ******************************************************* /** * Constructs a PreferencesAdapter on the given Preferences * using the specified key and default value, all which must be * non-{@code null}. * * @param prefs the Preferences used to store and retrieve * @param key the key used to get and set values in the Preferences * @param defaultValue the default value * * @throws NullPointerException if the Preferences, key, or default value * is {@code null} * @throws IllegalArgumentException if the default value is of a type other * than Boolean, Double, Float, Integer, Long, or String. */ public PreferencesAdapter( Preferences prefs, String key, Object defaultValue) { this.prefs = checkNotNull(prefs, "The Preferences must not be null."); this.key = checkNotNull(key, "The key must not be null."); this.defaultValue = checkNotNull(defaultValue, "The default value must not be null."); checkArgument(isBackedType(defaultValue), "The Default " + ERROR_MSG); this.type = defaultValue.getClass(); } // ValueModel Implementation ********************************************** /** * Looks up and returns the value from the preferences. The value is * look up under this adapter's key. It will be converted before it is * returned. * * @return the retrieved and converted value * @throws ClassCastException if the type of the default value * cannot be read from the preferences */ public Object getValue() { if (type == Boolean.class) { return Boolean.valueOf(getBoolean()); } else if (type == Double.class) { return Double.valueOf(getDouble()); } else if (type == Float.class) { return Float.valueOf(getFloat()); } else if (type == Integer.class) { return Integer.valueOf(getInt()); } else if (type == Long.class) { return Long.valueOf(getLong()); } else if (type == String.class) { return getString(); } else { throw new ClassCastException(ERROR_MSG); } } /** * Converts the given value to a string and puts it into the preferences. * * @param newValue the object to be stored * @throws IllegalArgumentException if the new value cannot be stored * in the preferences due to an illegal type */ public void setValue(Object newValue) { checkNotNull(newValue, "The value must not be null."); // Class valueType = newValue.getClass(); // if (type != valueType) // throw new IllegalArgumentException( // "The type of the value set must be consistent " // + "with the default value type " + type); // if (newValue instanceof Boolean) { setBoolean(((Boolean) newValue).booleanValue()); } else if (newValue instanceof Double) { setDouble(((Double) newValue).doubleValue()); } else if (newValue instanceof Float) { setFloat(((Float) newValue).floatValue()); } else if (newValue instanceof Integer) { setInt(((Integer) newValue).intValue()); } else if (newValue instanceof Long) { setLong(((Long) newValue).longValue()); } else if (newValue instanceof String) { setString((String) newValue); } } // Convenience Accessors ************************************************** /** * Looks up, converts and returns the stored value from the preferences. * Returns the default value if no value has been stored before. * * @return the stored value or the default */ public boolean getBoolean() { return prefs.getBoolean(key, ((Boolean) defaultValue).booleanValue()); } /** * Looks up, converts and returns the stored value from the preferences. * Returns the default value if no value has been stored before. * * @return the stored value or the default */ public double getDouble() { return prefs.getDouble(key, ((Double) defaultValue).doubleValue()); } /** * Looks up, converts and returns the stored value from the preferences. * Returns the default value if no value has been stored before. * * @return the stored value or the default */ public float getFloat() { return prefs.getFloat(key, ((Float) defaultValue).floatValue()); } /** * Looks up, converts and returns the stored value from the preferences. * Returns the default value if no value has been stored before. * * @return the stored value or the default */ public int getInt() { return prefs.getInt(key, ((Integer) defaultValue).intValue()); } /** * Looks up, converts and returns the stored value from the preferences. * Returns the default value if no value has been stored before. * * @return the stored value or the default */ public long getLong() { return prefs.getLong(key, ((Long) defaultValue).longValue()); } /** * Looks up, converts and returns the stored value from the preferences. * Returns the default value if no value has been stored before. * * @return the stored value or the default */ @Override public String getString() { return prefs.get(key, (String) defaultValue); } /** * Converts the given value to an Object and stores it in this * adapter's Preferences under this adapter's preferences key. * * @param newValue the value to put into the Preferences * * @throws ClassCastException if the default value is not a Boolean */ public void setBoolean(boolean newValue) { boolean oldValue = getBoolean(); prefs.putBoolean(key, newValue); fireValueChange(oldValue, newValue); } /** * Converts the given value to an Object and stores it in this * adapter's Preferences under this adapter's preferences key. * * @param newValue the value to put into the Preferences * * @throws ClassCastException if the default value is not a Double */ public void setDouble(double newValue) { double oldValue = getDouble(); prefs.putDouble(key, newValue); fireValueChange(oldValue, newValue); } /** * Converts the given value to an Object and stores it in this * adapter's Preferences under this adapter's preferences key. * * @param newValue the value to put into the Preferences * * @throws ClassCastException if the default value is not a Float */ public void setFloat(float newValue) { float oldValue = getFloat(); prefs.putFloat(key, newValue); fireValueChange(oldValue, newValue); } /** * Converts the given value to an Object and stores it in this * adapter's Preferences under this adapter's preferences key. * * @param newValue the value to put into the Preferences * * @throws ClassCastException if the default value is not an Integer */ public void setInt(int newValue) { int oldValue = getInt(); prefs.putInt(key, newValue); fireValueChange(oldValue, newValue); } /** * Converts the given value to an Object and stores it in this * adapter's Preferences under this adapter's preferences key. * * @param newValue the value to put into the Preferences * * @throws ClassCastException if the default value is not a Long */ public void setLong(long newValue) { long oldValue = getLong(); prefs.putLong(key, newValue); fireValueChange(oldValue, newValue); } /** * Converts the given value to an Object and stores it in this * adapter's Preferences under this adapter's preferences key. * * @param newValue the value to put into the Preferences * * @throws ClassCastException if the default value is not a String */ public void setString(String newValue) { String oldValue = getString(); prefs.put(key, newValue); fireValueChange(oldValue, newValue); } // Helper Code ************************************************************ /** * Used to check the default value type during construction. */ private boolean isBackedType(Object value) { Class aClass = value.getClass(); return aClass == Boolean.class || aClass == Double.class || aClass == Float.class || aClass == Integer.class || aClass == Long.class || aClass == String.class; } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/0000755000175000017500000000000011374522114023511 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/Trigger.java0000644000175000017500000001376611374522114025774 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import static com.jgoodies.common.base.Preconditions.checkArgument; /** * A ValueModel implementation that is intended to be used as trigger channel * for instances of BufferedValueModel. API users shall trigger commit and flush * events using #triggerCommit and #triggerFlush.

* * This Trigger class works around an inconvenient situation when using * a general ValueHolder as trigger channel of a BufferedValueModel. * BufferedValueHolder performs commit and flush events only if the trigger * channel value reports a change. And a ValueHolder doesn't report a change * if #setValue tries to set the current value. For example * if you set Boolean.TRUE twice, the latter doesn't fire * a property change event. The methods #triggerCommit and * #triggerFlush check for the current state and guarantee * that the appropriate PropertyChangeEvent is fired. * On the other hand, the implementation minimizes the number of events * necessary to commit or flush buffered values.

* * Constraints: The value is of type Boolean. *

* The following example delays the commit of a buffered value: *

 * ValueModel subject = new ValueHolder();
 * Trigger trigger = new Trigger();
 * BufferedValueModel buffer = new BufferedValueModel(subject, trigger);
 *
 * buffer.setValue("value");
 * ...
 * trigger.triggerCommit();
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.9 $ * * @see BufferedValueModel */ public final class Trigger extends AbstractValueModel { private static final Boolean COMMIT = Boolean.TRUE; private static final Boolean FLUSH = Boolean.FALSE; private static final Boolean NEUTRAL = null; /** * Holds the current trigger state. */ private Boolean value; // Instance Creation ****************************************************** /** * Constructs a Trigger set to neutral. */ public Trigger() { value = NEUTRAL; } // ValueModel Implementation ********************************************** /** * Returns a Boolean that indicates the current trigger state. * * @return a Boolean that indicates the current trigger state */ public Object getValue() { return value; } /** * Sets a new Boolean value and rejects all non-Boolean values. * Fires no change event if the new value is equal to the * previously set value.

* * This method is not intended to be used by API users. * Instead you should trigger commit and flush events by invoking * #triggerCommit or #triggerFlush. * * @param newValue the Boolean value to be set * @throws IllegalArgumentException if the newValue is not a Boolean */ public void setValue(Object newValue) { checkArgument(newValue == null || newValue instanceof Boolean, "Trigger values must be of type Boolean."); Object oldValue = value; value = (Boolean) newValue; fireValueChange(oldValue, newValue); } // Triggering ************************************************************* /** * Triggers a commit event in BufferedValueModels that share this Trigger. * Sets the value to Boolean.TRUE and ensures that listeners * are notified about a value change to this new value. If necessary * the value is temporarily set to {@code null}. This way it minimizes * the number of PropertyChangeEvents fired by this Trigger. */ public void triggerCommit() { if (COMMIT.equals(getValue())) { setValue(NEUTRAL); } setValue(COMMIT); } /** * Triggers a flush event in BufferedValueModels that share this Trigger. * Sets the value to Boolean.FALSE and ensures that listeners * are notified about a value change to the new value. If necessary * the value is temporarily set to {@code null}. This way it minimizes * the number of PropertyChangeEvents fired by this Trigger. */ public void triggerFlush() { if (FLUSH.equals(getValue())) { setValue(NEUTRAL); } setValue(FLUSH); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/AbstractConverter.java0000644000175000017500000001702511374522114030014 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; /** * An abstract class that minimizes the effort required to implement * a type converter. A type converter is a ValueModel that converts the type * of an object being held as a value in one ValueModel into another type.

* * More formally, a converting ValueModel VM1 converts the type * T2 of an object being held as a value in one ValueModel VM2 * into another type T1. When reading a value from VM1, * instances of T2 are read from VM2 and are converted to T1. When storing * a new value to VM1, the type converter will perform the inverse conversion * and will convert an instance of T1 to T2.

* * The conversion must be performed when reading and writing values, * as well as in the change notification. This class specifies abstract * methods for the conversion from source to output, which is used to * convert in #getValue and in the change notification. * For the write conversion you must implement #setValue.

* * Most converters can set values converted by #convertFromSubject * with #setValue. However, a converter may reject subject values * to be converted and may reject values to be set - as any ValueModel.

* * Type converters should be used judiciously and only to bridge two * ValueModels. Converters often use a generic but weak * conversion, and so can be limited w.r.t. to localized * formatting conventions.

* * When binding non-String values to a text UI component, consider * using a {@link javax.swing.JFormattedTextField}. Formatted text fields * provide a powerful means to convert strings to objects and handle many cases * that arise around invalid input. Formatted text fields can be bound * to ValueModels using the * {@link com.jgoodies.binding.beans.PropertyConnector} class. * * * @author Karsten Lentzsch * @version $Revision: 1.14 $ * * @see ValueModel * @see ConverterFactory * @see javax.swing.JFormattedTextField * @see com.jgoodies.binding.beans.PropertyConnector */ public abstract class AbstractConverter extends AbstractValueModel { /** * Holds the ValueModel that in turn holds the source value. */ protected final ValueModel subject; private final PropertyChangeListener subjectValueChangeHandler; // Instance creation ****************************************************** /** * Constructs an AbstractConverter on the given subject. * * @param subject the ValueModel that holds the source value * @throws NullPointerException if the subject is {@code null} */ public AbstractConverter(ValueModel subject) { this.subject = checkNotNull(subject, "The subject must not be null."); this.subjectValueChangeHandler = new SubjectValueChangeHandler(); subject.addValueChangeListener(subjectValueChangeHandler); } // Abstract Behavior ****************************************************** /** * Converts a value from the subject to the type or format used * by this converter. * * @param subjectValue the subject's value * @return the converted value in the type or format used by this converter */ public abstract Object convertFromSubject(Object subjectValue); // ValueModel Implementation ********************************************** /** * Converts the subject's value and returns the converted value. * * @return the converted subject value */ public Object getValue() { return convertFromSubject(subject.getValue()); } // Misc ******************************************************************* /** * Removes the internal subject value change handler from the subject. * The listener has been registered during construction. Useful to avoid * memory leaks, if the subject lives much longer than this converter. * As an alternative you can use event listener lists in your ValueModels * that implement references with {@link WeakReference}.

* * This converter must not be used anymore once #release has been called.

* * Subclasses that override this method must call this super implementation. * * @since 1.3 */ public void release() { subject.removeValueChangeListener(subjectValueChangeHandler); } // Helper Class *********************************************************** /** * Listens to subect value changes, converts this value and updates * the converter's value. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { /** * Notifies listeners about a change in the underlying subject. * The old and new value used in the PropertyChangeEvent to be fired * are converted versions of the observed old and new values. * The observed old and new value are converted only * if they are non-null. This is because {@code null} * may be a valid value or may indicate not available.

* * TODO: We may need to check the identity, not equity. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { Object oldValue = evt.getOldValue() == null ? null : convertFromSubject(evt.getOldValue()); Object newValue = evt.getNewValue() == null ? null : convertFromSubject(evt.getNewValue()); fireValueChange(oldValue, newValue); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/DelayedReadValueModel.java0000644000175000017500000002572511374522114030510 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.Timer; /** * A ValueModel that deferres updates and read-access for a specified delay. * Useful to coalesce frequent changes. For example if a heavy computation * shall be performed only for a "stable" selection after a series of * quick selection changes.

* * Wraps a given subject ValueModel and observes subject value changes * and forwards them to listeners of this model after a delay. If the subject * value changes, a Swing Timer is used to delay the change notification. * A previously started timer - if any - will be stopped before. * Reading this model's value returns: * a) the subject value if there's no pending update, or * b) this model's old value that will be updated after the delay. * If a value is set to this model, it immediately updates the subject value.

* * TODO: Describe how and when listeners get notified about the delayed change.

* * TODO: Write about the recommended delay time - above the double-click time * and somewhere below a second, e.g. 100ms to 200ms.

* * TODO: Write about a slightly different commit handling. The current * implementation defers the commit until the value is stable for the * specified delay; it's a DelayUntilStableForXXXmsValueModel. Another * feature is to delay for a specified time but ensure that some commits * and change notifications happen. The latter is a CoalescingWriteValueModel.

* * TODO: Summarize the differences between the DelayedReadValueModel, the * DelayedWriteValueModel, and the DelayedPropertyChangeHandler. * * @author Karsten Lentzsch * @version $Revision: 1.13 $ * * @see javax.swing.Timer * * @since 1.1 */ public final class DelayedReadValueModel extends AbstractValueModel { /** * Refers to the underlying subject ValueModel. */ private final ValueModel subject; /** * The Timer used to perform the delayed commit. */ private final Timer timer; /** * If {@code true} all pending updates will be coalesced. * In other words, an update will be fired if no updates * have been received for this model's delay. */ private boolean coalesce; /** * Holds this model's old value that is returned in getValue * during a pending change. most recent old value. It is set in * #fireDelayedValueChange. */ private Object oldValue; /** * Holds the most recent pending PropertyChangeEvent as provided * from the subject change notification that this model deferres. * #fireDelayedValueChange. */ private PropertyChangeEvent pendingEvt; // Instance Creation ****************************************************** /** * Constructs a DelayedReadValueModel for the given subject ValueModel * and the specified Timer delay in milliseconds with coalescing disabled. * * @param subject the underlying (or wrapped) ValueModel * @param delay the milliseconds to wait before a change * shall be committed * * @throws IllegalArgumentException if the delay is negative */ public DelayedReadValueModel(ValueModel subject, int delay) { this(subject, delay, false); } /** * Constructs a DelayedReadValueModel for the given subject ValueModel * and the specified Timer delay in milliseconds using the given * coalesce mode. * * @param subject the underlying (or wrapped) ValueModel * @param delay the milliseconds to wait before a change * shall be committed * @param coalesce {@code true} to coalesce all pending changes, * {@code false} to fire changes with the delay when an update * has been received * * @throws IllegalArgumentException if the delay is negative * * @see #setCoalesce(boolean) */ public DelayedReadValueModel(ValueModel subject, int delay, boolean coalesce) { this.subject = subject; this.coalesce = coalesce; this.timer = new Timer(delay, new ValueUpdateListener()); timer.setRepeats(false); subject.addValueChangeListener(new SubjectValueChangeHandler()); oldValue = subject.getValue(); } // ValueModel Implementation ****************************************** /** * Returns the subject's value or in case of a pending commit, * the pending new value. * * @return the subject's current or future value. */ public Object getValue() { return isPending() ? oldValue : subject.getValue(); } /** * Sets the given new value immediately as the subject's new value. * Note that change notifications from the subject are deferred * by this model. Therefore listeners registered with this model * will be notified after this model's delay. * * @param newValue the value to set */ public void setValue(Object newValue) { subject.setValue(newValue); } // Accessors ************************************************************** /** * Returns the delay, in milliseconds, that is used to defer value change * notifications. * * @return the delay, in milliseconds, that is used to defer * value change notifications * * @see #setDelay */ public int getDelay() { return timer.getDelay(); } /** * Sets the delay, in milliseconds, that is used to defer value change * notifications. * * @param delay the delay, in milliseconds, that is used to defer * value change notifications * @see #getDelay */ public void setDelay(int delay) { timer.setInitialDelay(delay); timer.setDelay(delay); } /** * Returns if this model coalesces all pending changes or not. * * @return {@code true} if all pending changes will be coalesced, * {@code false} if pending changes are fired with a delay * when an update has been received. * * @see #setCoalesce(boolean) */ public boolean isCoalesce() { return coalesce; } /** * Sets if this model shall coalesce all pending changes or not. * In this case, a change event will be fired first, * if no updates have been received for this model's delay. * If coalesce is {@code false}, a change event will be fired * with this model's delay when an update has been received.

* * The default value is {@code false}.

* * Note that this value is not the #coalesce value * of this model's internal Swing timer. * * @param b {@code true} to coalesce, * {@code false} to fire separate changes */ public void setCoalesce(boolean b) { coalesce = b; } // Misc ******************************************************************* /** * Stops a running timer. Pending changes - if any - are canceled * and won't be performed by the ValueUpdateListener. * * @since 1.2 */ public void stop() { timer.stop(); } /** * Checks and answers whether this model has one or more pending changes. * * @return {@code true} if there are pending changes, {@code false} if not. * * @since 2.0.4 */ public boolean isPending() { return timer.isRunning(); } /** * Sets the given new value after this model's delay. * Does nothing if the new value and the latest pending value are the same. * * @param evt the PropertyChangeEvent to be fired after this model's delay */ private void fireDelayedValueChange(PropertyChangeEvent evt) { pendingEvt = evt; if (coalesce) { timer.restart(); } else { timer.start(); } } // Event Handling ********************************************************* /** * Describes the delayed action to be performed by the timer. */ private final class ValueUpdateListener implements ActionListener { /** * An ActionEvent has been fired by the Timer after its delay. * Fires the pending PropertyChangeEvent, stops the timer, * and updates this model's oldValue.

* * TODO: Consider stopping the timer before firing the change, * because the change handling may take some time. */ public void actionPerformed(ActionEvent e) { fireValueChange(pendingEvt.getOldValue(), pendingEvt.getNewValue(), true); stop(); oldValue = pendingEvt.getNewValue() != null ? pendingEvt.getNewValue() : subject.getValue(); } } /** * Forwards value changes in the subject to listeners of this model. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { fireDelayedValueChange(evt); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/BufferedValueModel.java0000644000175000017500000004617511374522114030071 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; /** * A ValueModel that wraps another ValueModel, the subject, * and delays changes of the subject's value. Returns the subject's value * until a value has been set. The buffered value is not written to the * subject until the trigger channel changes to Boolean.TRUE. * The buffered value can be flushed by changing the trigger channel value * to Boolean.FALSE. Note that the commit and flush events * are performed only if the trigger channel fires a change event. Since a * plain ValueHolder fires no property change event if a value is set that has * been set before, it is recommended to use a {@link Trigger} instead * and invoke its #triggerCommit and triggerFlush * methods.

* * The BufferedValueModel has been designed to behave much like its subject * when accessing the value. Therefore it throws all exceptions that would * arise when accessing the subject directly. Hence, attempts to read or * write a value while the subject is {@code null} are always rejected * with a NullPointerException.

* * This class provides the bound read-write properties subject and * triggerChannel for the subject and trigger channel and a bound * read-only property buffering for the buffering state.

* * The BufferedValueModel registers listeners with the subject and * trigger channel. It is recommended to remove these listeners by invoking * #release if the subject and trigger channel live much longer * than this buffer. After #release has been called * you must not use the BufferedValueModel instance any longer. * As an alternative you may use event listener lists in subjects and * trigger channels that are based on WeakReferences.

* * If the subject value changes while this model is in buffering state * this change won't show through as this model's new value. If you want * to update the value whenever the subject value changes, register a * listener with the subject value and flush this model's trigger.

* * Constraints: The subject is of type Object, * the trigger channel value of type Boolean. * * @author Karsten Lentzsch * @version $Revision: 1.14 $ * * @see ValueModel * @see ValueModel#getValue() * @see ValueModel#setValue(Object) */ public final class BufferedValueModel extends AbstractValueModel { // Names of the bound bean properties ************************************* /** * The name of the bound read-only bean property that indicates * whether this models is buffering or in write-through state. * * @see #isBuffering() */ public static final String PROPERTYNAME_BUFFERING = "buffering"; /** * The name of the bound read-write bean property for the subject. * * @see #getSubject() * @see #setSubject(ValueModel) */ public static final String PROPERTYNAME_SUBJECT = "subject"; /** * The name of the bound read-write bean property for the trigger channel. * * @see #getTriggerChannel() * @see #setTriggerChannel(ValueModel) */ public static final String PROPERTYNAME_TRIGGER_CHANNEL = "triggerChannel"; // ************************************************************************ /** * Holds the subject that provides the underlying value * of type Object. */ private ValueModel subject; /** * Holds the three-state trigger of type Boolean. */ private ValueModel triggerChannel; /** * Holds the buffered value. This value is ignored if we are not buffering. */ private Object bufferedValue; /** * Indicates whether a value has been assigned since the last trigger change. */ private boolean valueAssigned; /** * Holds a PropertyChangeListener that observes subject value changes. */ private final ValueChangeHandler valueChangeHandler; /** * Holds a PropertyChangeListener that observes trigger changes. */ private final TriggerChangeHandler triggerChangeHandler; // Instance Creation **************************************************** /** * Constructs a BufferedValueModel on the given subject * using the given trigger channel. * * @param subject the value model to be buffered * @param triggerChannel the value model that triggers the commit or flush event * @throws NullPointerException if the triggerChannel is {@code null} */ public BufferedValueModel( ValueModel subject, ValueModel triggerChannel) { valueChangeHandler = new ValueChangeHandler(); triggerChangeHandler = new TriggerChangeHandler(); setSubject(subject); setTriggerChannel(triggerChannel); setBuffering(false); } // Accessing the Subject and Trigger Channel ****************************** /** * Returns the subject, i.e. the underlying ValueModel that provides * the unbuffered value. * * @return the ValueModel that provides the unbuffered value */ public ValueModel getSubject() { return subject; } /** * Sets a new subject ValueModel, i.e. the model that provides * the unbuffered value. Notifies all listeners that the subject * property has changed. * * @param newSubject the subject ValueModel to be set */ public void setSubject(ValueModel newSubject) { ValueModel oldSubject = getSubject(); ReadAccessResult oldReadValue = readBufferedOrSubjectValue(); Object oldValue = oldReadValue.value; if (oldSubject != null) { oldSubject.removeValueChangeListener(valueChangeHandler); } subject = newSubject; if (newSubject != null) { newSubject.addValueChangeListener(valueChangeHandler); } firePropertyChange(PROPERTYNAME_SUBJECT, oldSubject, newSubject); if (isBuffering()) { return; } ReadAccessResult newReadValue = readBufferedOrSubjectValue(); Object newValue = newReadValue.value; // TODO: Check if the following conditional is valid. // Note that the old and/or new value may be null // just because the property is read-only. if (oldValue != null || newValue != null) { fireValueChange(oldValue, newValue, true); } } /** * Returns the ValueModel that is used to trigger commit and flush events. * * @return the ValueModel that is used to trigger commit and flush events */ public ValueModel getTriggerChannel() { return triggerChannel; } /** * Sets the ValueModel that triggers the commit and flush events. * * @param newTriggerChannel the ValueModel to be set as trigger channel * @throws NullPointerException if the newTriggerChannel is {@code null} */ public void setTriggerChannel(ValueModel newTriggerChannel) { checkNotNull(newTriggerChannel, "The trigger channel must not be null."); ValueModel oldTriggerChannel = getTriggerChannel(); if (oldTriggerChannel != null) { oldTriggerChannel.removeValueChangeListener(triggerChangeHandler); } triggerChannel = newTriggerChannel; //if (newTriggerChannel != null) { newTriggerChannel.addValueChangeListener(triggerChangeHandler); //} firePropertyChange(PROPERTYNAME_TRIGGER_CHANNEL, oldTriggerChannel, newTriggerChannel); } // Implementing the ValueModel Interface ******************************** /** * Returns the subject's value if no value has been set since the last * commit or flush, and returns the buffered value otherwise. * Attempts to read a value when no subject is set are rejected * with a NullPointerException. * * @return the buffered value * @throws NullPointerException if no subject is set */ public Object getValue() { checkNotNull(subject, "The subject must not be null " + "when reading a value from a BufferedValueModel."); return isBuffering() ? bufferedValue : subject.getValue(); } /** * Sets a new buffered value and turns this BufferedValueModel into * the buffering state. The buffered value is not provided to the * underlying model until the trigger channel indicates a commit. * Attempts to set a value when no subject is set are rejected * with a NullPointerException.

* * The above semantics is easy to understand, however it is tempting * to check the new value against the current subject value to avoid * that the buffer unnecessary turns into the buffering state. But here's * a problem. Let's say the subject value is "first" at buffer * creation time, and let's say the subject value has changed in the * meantime to "second". Now someone sets the value "second" to this buffer. * The subject value and the value to be set are equal. Shall we buffer? * Also, this decision would depend on the ability to read the subject. * The semantics would depend on the subject' state and capabilities.

* * It is often sufficient to observe the buffering state when enabling * or disabling a commit command button like "OK" or "Apply". * And later check the changed state in a PresentationModel. * You may want to do better and may want to observe a property like * "defersTrueChange" that indicates whether flushing a buffer will * actually change the subject. But note that such a state may change * with subject value changes, which may be hard to understand for a user.

* * TODO: Consider adding an optimized execution path for the case * that this model is already in buffering state. In this case * the old buffered value can be used instead of invoking * #readBufferedOrSubjectValue(). * * @param newBufferedValue the value to be buffered * @throws NullPointerException if no subject is set */ public void setValue(Object newBufferedValue) { checkNotNull(subject, "The subject must not be null " + "when setting a value to a BufferedValueModel."); ReadAccessResult oldReadValue = readBufferedOrSubjectValue(); Object oldValue = oldReadValue.value; bufferedValue = newBufferedValue; setBuffering(true); if (oldReadValue.readable && oldValue == newBufferedValue) { return; } fireValueChange(oldValue, newBufferedValue, true); } /** * Tries to lookup the current buffered or subject value * and returns this value plus a marker that indicates * whether the read-access succeeded or failed. * The latter situation arises in an attempt to read a value from * a write-only subject if this BufferedValueModel is not buffering * and if this model changes its subject. * * @return the current value plus a boolean that indicates the success or failure */ private ReadAccessResult readBufferedOrSubjectValue() { try { Object value = getValue(); // May fail with write-only models return new ReadAccessResult(value, true); } catch (Exception e) { return new ReadAccessResult(null, false); } } // Releasing PropertyChangeListeners ************************************** /** * Removes the PropertyChangeListeners from the subject and * trigger channel.

* * To avoid memory leaks it is recommended to invoke this method * if the subject and trigger channel live much longer than this buffer. * Once #release has been invoked the BufferedValueModel instance * must not be used any longer.

* * As an alternative you may use event listener lists in subjects and * trigger channels that are based on WeakReferences. * * @see java.lang.ref.WeakReference */ public void release() { ValueModel aSubject = getSubject(); if (aSubject != null) { aSubject.removeValueChangeListener(valueChangeHandler); } ValueModel aTriggerChannel = getTriggerChannel(); if (aTriggerChannel != null) { aTriggerChannel.removeValueChangeListener(triggerChangeHandler); } } // Misc ***************************************************************** /** * Returns whether this model buffers a value or not, that is, whether * a value has been assigned since the last commit or flush. * * @return true if a value has been assigned since the last commit or flush */ public boolean isBuffering() { return valueAssigned; } private void setBuffering(boolean newValue) { boolean oldValue = isBuffering(); valueAssigned = newValue; firePropertyChange(PROPERTYNAME_BUFFERING, oldValue, newValue); } /** * Sets the buffered value as new subject value - if any value has been set. * After this commit this BufferedValueModel behaves as if no value * has been set before. This method is invoked if the trigger has changed * to {@code Boolean.TRUE}.

* * Since the subject's value is assigned after the buffer marker * is reset, subject change notifications will be handled. In this case * the subject's old value is not this BufferedValueModel's old value; * instead the old value reported to listeners of this model * is the formerly buffered value. * * @throws NullPointerException if no subject is set */ private void commit() { if (isBuffering()) { setBuffering(false); valueChangeHandler.oldValue = bufferedValue; subject.setValue(bufferedValue); valueChangeHandler.oldValue = null; } else { checkNotNull(subject, "The subject must not be null " + "while committing a value in a BufferedValueModel."); } } /** * Flushes the buffered value. This method is invoked if the trigger * has changed to {@code Boolean.FALSE}. After this flush * this BufferedValueModel behaves as if no value has been set before.

* * TODO: Check whether we need to use #getValueSafe instead of #getValue. * * @throws NullPointerException if no subject is set */ private void flush() { Object oldValue = getValue(); setBuffering(false); Object newValue = getValue(); fireValueChange(oldValue, newValue, true); } // Overriding Superclass Behavior ***************************************** @Override protected String paramString() { return "value=" + valueString() + "; buffering" + isBuffering(); } // Helper Class *********************************************************** /** * Describes the result of a subject value read-access plus a marker * that indicates if the value could be read or not. The latter is * used in #setValue to suppress some unnecessary * change notifications in case the value could be read successfully. * * @see BufferedValueModel#setValue(Object) */ private static final class ReadAccessResult { final Object value; final boolean readable; private ReadAccessResult(Object value, boolean readable) { this.value = value; this.readable = readable; } } // Event Handling ********************************************************* /** * Listens to changes of the subject. */ private final class ValueChangeHandler implements PropertyChangeListener { Object oldValue; /** * The subject's value has changed. Notifies this BufferedValueModel's * listeners iff we are not buffering, does nothing otherwise.

* * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { if (!isBuffering()) { fireValueChange( oldValue != null ? oldValue : evt.getOldValue(), evt.getNewValue(), true); } } } /** * Listens to changes of the trigger channel. */ private final class TriggerChangeHandler implements PropertyChangeListener { /** * The trigger has been changed. Commits or flushes the buffered value. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { if (Boolean.TRUE.equals(evt.getNewValue())) { commit(); } else if (Boolean.FALSE.equals(evt.getNewValue())) { flush(); } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/package.html0000644000175000017500000000522711374522114026000 0ustar twernertwerner Contains the {@link com.jgoodies.binding.value.ValueModel} interface and hierarchy. The contained abstract classes minimize the effort to implement new ValueModels.

Related Documentation

For more information see: @see com.jgoodies.binding @see com.jgoodies.binding.adapter @see com.jgoodies.binding.beans @see com.jgoodies.binding.formatter @see com.jgoodies.binding.list jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/ValueModel.java0000644000175000017500000001266011374522114026416 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import java.beans.PropertyChangeListener; /** * Describes models with a generic access to a single value that allow * to observe value changes. The value can be accessed using the * #getValue()/#setValue(Object) methods. * Observers can register instances of PropertyChangeListener * to be notified if the value changes.

* * If the value is read-only or write-only, an implementor may choose * to reject an operation using an UnsupportedOperationException * or may do nothing or perform an appropriate action, or may return an * appropriate value.

* * The listeners registered with this ValueModel using #addValueChangeListener * will be invoked only with PropertyChangeEvents that have the name set to * "value". * In other words, the listeners won't get notified when a PropertyChangeEvent * is fired that has a null object as the name to indicate an arbitrary set * of the event source's properties have changed. This is the case * if you use the PropertyChangeSupport, either directly or indirectly, * to fire property changes with the property name "value" specified. * This constraint ensures that all ValueModel implementors behave * like the AbstractValueModel subclasses. * In the rare case, where you want to notify a PropertyChangeListener * even with PropertyChangeEvents that have no property name set, * you can register the listener with #addPropertyChangeListener, * not #addValueChangeListener.

* * AbstractValueModel minimizes the effort required to implement this interface. * It uses the PropertyChangeSupport to fire PropertyChangeEvents, and it adds * PropertyChangeListeners for the specific property name "value". This ensures * that the constraint mentioned above is met.

* * Implementors are encouraged to provide non-null values for the * PropertyChangeEvent's old and new values. However, both may be null. * * @author Karsten Lentzsch * @version $Revision: 1.8 $ * * @see AbstractValueModel * @see ValueHolder * @see com.jgoodies.binding.beans.PropertyAdapter */ public interface ValueModel { /** * Returns this model's value. In case of a write-only value, * implementors may choose to either reject this operation or * or return {@code null} or any other appropriate value. * * @return this model's value */ Object getValue(); /** * Sets a new value and notifies any registered value listeners * if the value has changed. In case of a read-only value * implementors may choose to either reject this operation * or to do nothing. * * @param newValue the value to be set */ void setValue(Object newValue); /** * Registers the given PropertyChangeListener with this * ValueModel. The listener will be notified if the value has changed. * The PropertyChangeEvents delivered to the listener must have the name * set to "value". The latter ensures that all ValueModel implementors * behave like the AbstractValueModel subclasses.

* * To comply with the above specification implementors can use * the PropertyChangeSupport's #addPropertyChangeListener method * that accepts a property name, so that listeners will be invoked only * if that specific property has changed. * * @param listener the listener to be added * * @see AbstractValueModel#addValueChangeListener(PropertyChangeListener) */ void addValueChangeListener(PropertyChangeListener listener); /** * Deregisters the given PropertyChangeListener from this ValueModel. * * @param listener the listener to be removed */ void removeValueChangeListener(PropertyChangeListener listener); } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/AbstractVetoableValueModel.java0000644000175000017500000001375311374522114031570 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; /** * A ValueModel that allows to accept or reject proposed value changes. * Useful to request information from the user or to perform operations * before a value is changed.

* * Wraps a given subject ValueModel and always returns the subject value * as this model's value. Observes subject value changes and forwards * them to listeners of this model. If a value is set to this model, * the abstract method #proposedChange is invoked. In this method * implementors define how to accept or reject value changes.

* * Implementors may veto against a proposed change based on the application * state or by asking the user, and may also perform additional operations * during the check, for example to save editor contents. Here's an example: *

 * public class CheckPendingEditValueModel extends AbstractVetoableValueModel {
 *
 *     public CheckPendingEditValueModel(ValueModel subject) {
 *         super(subject);
 *     }
 *
 *     public boolean proposedChange(Object oldValue, Object proposedNewValue) {
 *         if (equals(oldValue, proposedNewValue))
 *             return true;
 *         int option = JOptionPane.showConfirmDialog(
 *             Application.getDefaultParentFrame(),
 *             "Do you want to save the editor contents.");
 *         if (option == JOptionPane.YES_OPTION)
 *             model.save();
 *         return option != JOptionPane.CANCEL_OPTION;
 *     }
 * }
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @since 1.1 */ public abstract class AbstractVetoableValueModel extends AbstractValueModel { /** * Holds the wrapped subject ValueModel that is used to read values from * and commit accepted changes to. */ private final ValueModel subject; // Instance Creation ****************************************************** /** * Constructs an AbstractVetoableValueModel for the given ValueModel. * * @param subject the underlying (or wrapped) ValueModel * * @throws NullPointerException if the subject is {@code null} */ protected AbstractVetoableValueModel(ValueModel subject) { this.subject = checkNotNull(subject, "The subject must not be null."); subject.addValueChangeListener(new SubjectValueChangeHandler()); } // Abstract Behavior ****************************************************** /** * Checks and answers whether the proposed value change shall be * accepted or rejected. Implementors may perform additional * operations, for example to save a pending editor content. * * @param oldValue the value before the change * @param proposedNewValue the new value if the change is accepted * @return true to accept the proposed value, false to veto against it. */ public abstract boolean proposedChange(Object oldValue, Object proposedNewValue); // ValueModel Implementation ********************************************** /** * Returns this model's current subject value. * * @return this model's current subject value. */ public final Object getValue() { return subject.getValue(); } /** * Sets the given value as new subject value if and only if * 1) the new value differs from the old value and * 2) the proposed change is accepted as checked by * proposedChange(oldValue, newValue). * * @param newValue the value to set */ public final void setValue(Object newValue) { Object oldValue = getValue(); if (oldValue == newValue) { return; } if (proposedChange(oldValue, newValue)) { subject.setValue(newValue); } } // Event Handling ***************************************************** /** * Forwards value changes in the subject to listeners of this model. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { fireValueChange(evt.getOldValue(), evt.getNewValue(), true); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/ComponentValueModel.java0000644000175000017500000002271111374522114030277 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.adapter.BasicComponentFactory; import com.jgoodies.binding.adapter.Bindings; /** * A ValueModel that provides relevant GUI state in presentation models. * It provides bound properties for the frequently used JComponent state * enabled,visible and JTextComponent state editable. * ComponentValueModels can be used to set these properties at the * presentation model layer; any ComponentValueModel property change * will be reflected by components bound to that ComponentValueModel.

* * The ComponentValueModel is similar to the Swing Action class. * If you disable an Action, all buttons and menu items bound to that Action * will be disabled. If you disable a ComponentValueModel, all components * bound to that ComponentValueModel will be disabled. If you set the * ComponentValueModel to invisible, the component bound to it will become * invisible. If you set a ComponentValueModel to non-editable, * the JTextComponents bound to it will become non-editable.

* * Since version 1.1, PresentationModels can vend ComponentValueModels * using #getComponentModel(String) and * #getBufferedComponentModel(String). Multiple calls * to these factory methods return the same ComponentValueModel.

* * The BasicComponentFactory and the Bindings class check if the ValueModel * provided to create/bind a Swing component is a ComponentValueModel. * If so, the ComponentValueModel properties will be synchronized * with the associated Swing component properties.

* * It is recommended to use ComponentValueModels only for those models * that are bound to view components that require GUI state changes.

* * Example Code:

 * final class AlbumView {
 *
 *  ...
 *
 *     private void initComponents() {
 *         // No state modifications required for the name field.
 *         nameField = BasicComponentFactory.createTextField(
 *             presentationModel.getModel(Album.PROPERTYNAME_NAME));
 *         ...
 *         // Enablement shall change for the composer field
 *         composerField = BasicComponentFactory.createTextField(
 *             presentationModel.getComponentModel(Album.PROPERTYNAME_COMPOSER));
 *         ...
 *     }
 *
 *  ...
 *
 * }
 *
 *
 * public final class AlbumPresentationModel extends PresentationModel {
 *
 *  ...
 *
 *     private void updateComposerEnablement(boolean enabled) {
 *         getComponentModel(Album.PROPERTYNAME_COMPOSER).setEnabled(enabled);
 *     }
 *
 *  ...
 *
 * }
 * 

* * As of the Binding version 2.0 the ComponentValueModel feature * is implemented for text components, radio buttons, check boxes, * combo boxes, and lists. JColorChoosers bound using the * Bindings class will ignore ComponentValueModel state. * * @author Karsten Lentzsch * @version $Revision: 1.14 $ * * @see PresentationModel#getComponentModel(String) * @see PresentationModel#getBufferedComponentModel(String) * @see BasicComponentFactory * @see Bindings * * @since 1.1 */ public final class ComponentValueModel extends AbstractValueModel { // Names of the Bean Properties ******************************************* /** * The name of the property used to synchronize * this model with the enabled property of JComponents. */ public static final String PROPERTYNAME_ENABLED = "enabled"; /** * The name of the property used to synchronize * this model with the visible property of JComponents. */ public static final String PROPERTYNAME_VISIBLE = "visible"; /** * The name of the property used to synchronize * this model with the editable property of JTextComponents. */ public static final String PROPERTYNAME_EDITABLE = "editable"; // Instance Fields ******************************************************** /** * Holds the wrapped subject ValueModel that is used * to read and write value. */ private final ValueModel subject; private boolean enabled; private boolean visible; private boolean editable; // Instance Creation ****************************************************** /** * Constructs a ComponentValueModel for the given ValueModel. * * @param subject the underlying (or wrapped) ValueModel */ public ComponentValueModel(ValueModel subject) { this.subject = checkNotNull(subject, "The subject must not be null."); this.enabled = true; this.editable = true; this.visible = true; subject.addValueChangeListener(new SubjectValueChangeHandler()); } // ValueModel Implementation ********************************************** /** * Returns this model's current subject value. * * @return this model's current subject value. */ public Object getValue() { return subject.getValue(); } /** * Sets the given value as new subject value. * * @param newValue the value to set */ public void setValue(Object newValue) { subject.setValue(newValue); } // Firing Component Property Change Events ******************************** /** * Returns if this model represents an enabled or disabled component state. * * @return true for enabled, false for disabled */ public boolean isEnabled() { return enabled; } /** * Enables or disabled this model, which in turn * will enable or disable all Swing components bound to this model. * * @param b true to enable, false to disable. */ public void setEnabled(boolean b) { boolean oldEnabled = isEnabled(); enabled = b; firePropertyChange(PROPERTYNAME_ENABLED, oldEnabled, b); } /** * Returns if this model represents the visible or invisible component state. * * @return true for visible, false for invisible */ public boolean isVisible() { return visible; } /** * Sets this model state to visible or invisible, which in turn * will make all Swing components bound to this model visible or invisible. * * @param b true for visible, false for invisible */ public void setVisible(boolean b) { boolean oldVisible = isVisible(); visible = b; firePropertyChange(PROPERTYNAME_VISIBLE, oldVisible, b); } /** * Returns if this model represents the editable or non-editable * text component state. * * @return true for editable, false for non-editable */ public boolean isEditable() { return editable; } /** * Sets this model state to editable or non-editable, which in turn will * make all text components bound to this model editable or non-editable. * * @param b true for editable, false for non-editable */ public void setEditable(boolean b) { boolean oldEditable = isEditable(); editable = b; firePropertyChange(PROPERTYNAME_EDITABLE, oldEditable, b); } // Event Handling ********************************************************* /** * Forwards value changes in the subject to listeners of this model. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { fireValueChange(evt.getOldValue(), evt.getNewValue(), true); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/AbstractValueModel.java0000644000175000017500000003217411374522114030104 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import java.beans.PropertyChangeListener; import com.jgoodies.binding.beans.Model; /** * An abstract class that minimizes the effort required to implement * the {@link ValueModel} interface. It provides convenience methods * to convert boolean, double, float, int, and long to their * corresponding Object values and vice versa.

* * Subclasses must implement getValue() and * setValue(Object) to get and set this model's value. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @see com.jgoodies.binding.beans.ExtendedPropertyChangeSupport */ public abstract class AbstractValueModel extends Model implements ValueModel { /** * The name of the bound property value. */ public static final String PROPERTYNAME_VALUE = "value"; // Change Management **************************************************** /** * Registers the given PropertyChangeListener with this model. * The listener will be notified if the value has changed.

* * The PropertyChangeEvents delivered to the listener have the name * set to "value". In other words, the listeners won't get notified * when a PropertyChangeEvent is fired that has a null object as * the name to indicate an arbitrary set of the event source's * properties have changed.

* * In the rare case, where you want to notify a PropertyChangeListener * even with PropertyChangeEvents that have no property name set, * you can register the listener with #addPropertyChangeListener, * not #addValueChangeListener. * * @param l the listener to add * * @see ValueModel */ public final void addValueChangeListener(PropertyChangeListener l) { addPropertyChangeListener(PROPERTYNAME_VALUE, l); } /** * Removes the given PropertyChangeListener from the model. * * @param l the listener to remove */ public final void removeValueChangeListener(PropertyChangeListener l) { removePropertyChangeListener(PROPERTYNAME_VALUE, l); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the value before the change * @param newValue the value after the change * * @see java.beans.PropertyChangeSupport */ public final void fireValueChange(Object oldValue, Object newValue) { firePropertyChange(PROPERTYNAME_VALUE, oldValue, newValue); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method.

* * The boolean parameter specifies whether differences between the old * and new value are tested using == or #equals. * * @param oldValue the value before the change * @param newValue the value after the change * @param checkIdentity true to compare the old and new value using * ==, false to use #equals * * @see java.beans.PropertyChangeSupport */ public final void fireValueChange(Object oldValue, Object newValue, boolean checkIdentity) { firePropertyChange(PROPERTYNAME_VALUE, oldValue, newValue, checkIdentity); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the boolean value before the change * @param newValue the boolean value after the change * * @see java.beans.PropertyChangeSupport */ public final void fireValueChange(boolean oldValue, boolean newValue) { fireValueChange(Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the int value before the change * @param newValue the int value after the change * * @see java.beans.PropertyChangeSupport */ public final void fireValueChange(int oldValue, int newValue) { fireValueChange(Integer.valueOf(oldValue), Integer.valueOf(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the long value before the change * @param newValue the long value after the change * * @see java.beans.PropertyChangeSupport */ public final void fireValueChange(long oldValue, long newValue) { fireValueChange(Long.valueOf(oldValue), Long.valueOf(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the double value before the change * @param newValue the double value after the change * * @see java.beans.PropertyChangeSupport */ public final void fireValueChange(double oldValue, double newValue) { fireValueChange(Double.valueOf(oldValue), Double.valueOf(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the float value before the change * @param newValue the float value after the change * * @see java.beans.PropertyChangeSupport */ public final void fireValueChange(float oldValue, float newValue) { fireValueChange(Float.valueOf(oldValue), Float.valueOf(newValue)); } // Type Conversion ****************************************************** /** * Converts this model's value and returns it as a boolean. * * @return the boolean value * @throws ClassCastException if the observed value is not of type * Boolean * @throws NullPointerException if the value is {@code null} */ public final boolean booleanValue() { return ((Boolean) getValue()).booleanValue(); } /** * Converts this model's value and returns it as a double. * * @return the double value * @throws ClassCastException if the observed value is not of type * Double * @throws NullPointerException if the value is {@code null} */ public final double doubleValue() { return ((Double) getValue()).doubleValue(); } /** * Converts this model's value and returns it as a float. * * @return the float value * @throws ClassCastException if the observed value is not of type * Float * @throws NullPointerException if the value is {@code null} */ public final float floatValue() { return ((Float) getValue()).floatValue(); } /** * Converts this model's value and returns it as an int. * * @return the int value * @throws ClassCastException if the observed value is not of type * Integer * @throws NullPointerException if the value is {@code null} */ public final int intValue() { return ((Integer) getValue()).intValue(); } /** * Converts this model's value and returns it as a long. * * @return the long value * @throws ClassCastException if the observed value is not of type * Long * @throws NullPointerException if the value is {@code null} */ public final long longValue() { return ((Long) getValue()).longValue(); } /** * Converts this model's value and returns it as a String. * * @return this model's value as String * @throws ClassCastException if the observed value is not of type * String */ public String getString() { return (String) getValue(); } /** * Converts the given boolean to a Boolean and * sets it as new value. * * @param b the value to be converted and set as new value */ public final void setValue(boolean b) { setValue(Boolean.valueOf(b)); } /** * Converts the given double to a Double and * sets it as new value. * * @param d the value to be converted and set as new value */ public final void setValue(double d) { setValue(Double.valueOf(d)); } /** * Converts the given float to a Float and * sets it as new value. * * @param f the value to be converted and set as new value */ public final void setValue(float f) { setValue(Float.valueOf(f)); } /** * Converts the given int to an Integer and * sets it as new value. * * @param i the value to be converted and set as new value */ public final void setValue(int i) { setValue(Integer.valueOf(i)); } /** * Converts the given long to a Long and * sets it as new value. * * @param l the value to be converted and set as new value */ public final void setValue(long l) { setValue(Long.valueOf(l)); } /** * Returns a string representation of this value model. * Answers the print string of the observed value. * * @return a string representation of this value model */ @Override public String toString() { return getClass().getName() + "[" + paramString() + "]"; } /** * Returns a string representing the state of this model. * This method is intended to be used only for debugging purposes, * and the content and format of the returned string may vary between * implementations. The returned string may be empty but may not be * {@code null}. * * @return a string representation of this model's state * @since 2.0.3 */ protected String paramString() { return "value=" + valueString(); } /** * Returns a string representing the value of this model. * This method is intended to be used for debugging purposes only. * * @return a string representation of this model's value * @since 2.0.3 */ protected String valueString() { try { Object value = getValue(); return value == null ? "null" : value.toString(); } catch (Exception e) { return "Can't read"; } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/ConverterFactory.java0000644000175000017500000010214011374522114027651 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.text.Format; import java.text.ParseException; /** * A factory that vends ValueModels that convert types, for example * Dates to Strings. More formally, a converting ValueModel VM1 * converts the type T2 of an object being held as a value in * one ValueModel VM2 into another type T1. * When reading a value from VM1, instances of T2 are read from VM2 * and are converted to T1. When storing a new value to VM1, * the type converter will perform the inverse conversion and * will convert an instance of T1 to T2.

* * Type converters should be used judiciously and only to bridge two * ValueModels. To bind non-Strings to a text UI component * you should better use a {@link javax.swing.JFormattedTextField}. * They provide a more powerful means to convert strings to objects * and handle many cases that arise around invalid input. See also the classes * {@link com.jgoodies.binding.adapter.Bindings} and * {@link com.jgoodies.binding.adapter.BasicComponentFactory} on how to * bind ValueModels to formatted text fields.

* * The inner converter implementations have a 'public' visibility * to enable reflection access. * * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @see ValueModel * @see Format * @see javax.swing.JFormattedTextField */ public final class ConverterFactory { private ConverterFactory() { // Overrides default constructor; prevents instantiation. } // Factory Methods ******************************************************** /** * Creates and returns a ValueModel that negates Booleans and leaves * null unchanged.

* * Constraints: The subject is of type Boolean. * * @param booleanSubject a Boolean ValueModel * @return a ValueModel that inverts Booleans * * @throws NullPointerException if the subject is {@code null} */ public static ValueModel createBooleanNegator( ValueModel booleanSubject) { return new BooleanNegator(booleanSubject); } /** * Creates and returns a ValueModel that converts Booleans * to the associated of the two specified strings, and vice versa. * Null values are mapped to an empty string. * Ignores cases when setting a text.

* * Constraints: The subject is of type Boolean. * * @param booleanSubject a Boolean ValueModel * @param trueText the text associated with Boolean.TRUE * @param falseText the text associated with Boolean.FALSE * * @return a ValueModel that converts boolean to the associated text * * @throws NullPointerException if the subject, trueText or falseText * is {@code null} * @throws IllegalArgumentException if the trueText equals the falseText */ public static ValueModel createBooleanToStringConverter( ValueModel booleanSubject, String trueText, String falseText) { return createBooleanToStringConverter( booleanSubject, trueText, falseText, ""); } /** * Creates and returns a ValueModel that converts Booleans * to the associated of the two specified strings, and vice versa. * Null values are mapped to the specified text. * Ignores cases when setting a text.

* * Constraints: The subject is of type Boolean. * * @param booleanSubject a Boolean ValueModel * @param trueText the text associated with Boolean.TRUE * @param falseText the text associated with Boolean.FALSE * @param nullText the text associated with {@code null} * * @return a ValueModel that converts boolean to the associated text * * @throws NullPointerException if the subject, trueText, falseText * or nullText is {@code null} * @throws IllegalArgumentException if the trueText equals the falseText */ public static ValueModel createBooleanToStringConverter( ValueModel booleanSubject, String trueText, String falseText, String nullText) { return new BooleanToStringConverter(booleanSubject, trueText, falseText, nullText); } /** * Creates and returns a ValueModel that converts Doubles using the * specified multiplier.

* * Examples: multiplier=100, Double(1.23) -> Double(123), * multiplier=1000, Double(1.23) -> Double(1230)

* * Constraints: The subject is of type Double. * * @param doubleSubject a Double ValueModel * @param multiplier the multiplier used for the conversion * * @return a ValueModel that converts Doubles using the specified multiplier * * @throws NullPointerException if the subject is {@code null} * * @since 1.0.2 */ public static ValueModel createDoubleConverter( ValueModel doubleSubject, double multiplier) { return new DoubleConverter(doubleSubject, multiplier); } /** * Creates and returns a ValueModel that converts Doubles to Integer, * and vice versa.

* * Constraints: The subject is of type Double. * * @param doubleSubject a Double ValueModel * * @return a ValueModel that converts Doubles to Integer * * @throws NullPointerException if the subject is {@code null} */ public static ValueModel createDoubleToIntegerConverter( ValueModel doubleSubject) { return createDoubleToIntegerConverter(doubleSubject, 1); } /** * Creates and returns a ValueModel that converts Doubles to Integer, * and vice versa. The multiplier can be used to convert Doubles * to percent, permill, etc. For a percentage, set the multiplier to be 100, * for a permill, set the multiplier to be 1000.

* * Examples: multiplier=100, Double(1.23) -> Integer(123), * multiplier=1000, Double(1.23) -> Integer(1230)

* * Constraints: The subject is of type Double. * * @param doubleSubject a Double ValueModel * @param multiplier the multiplier used to convert the Double to Integer * * @return a ValueModel that converts Doubles to Integer * * @throws NullPointerException if the subject is {@code null} */ public static ValueModel createDoubleToIntegerConverter( ValueModel doubleSubject, int multiplier) { return new DoubleToIntegerConverter(doubleSubject, multiplier); } /** * Creates and returns a ValueModel that converts Floats using the * specified multiplier.

* * Examples: multiplier=100, Float(1.23) -> Float(123), * multiplier=1000, Float(1.23) -> Float(1230)

* * Constraints: The subject is of type Float. * * @param floatSubject a Float ValueModel * @param multiplier the multiplier used for the conversion * * @return a ValueModel that converts Float using the specified multiplier * * @throws NullPointerException if the subject is {@code null} * * @since 1.0.2 */ public static ValueModel createFloatConverter( ValueModel floatSubject, float multiplier) { return new FloatConverter(floatSubject, multiplier); } /** * Creates and returns a ValueModel that converts Floats to Integer, * and vice versa.

* * Constraints: The subject is of type Float. * s * @param floatSubject a Float ValueModel * * @return a ValueModel that converts Floats to Integer * * @throws NullPointerException if the subject is {@code null} */ public static ValueModel createFloatToIntegerConverter(ValueModel floatSubject) { return createFloatToIntegerConverter(floatSubject, 1); } /** * Creates and returns a ValueModel that converts Floats to Integer, * and vice versa. The multiplier can be used to convert Floats * to percent, permill, etc. For a percentage, set the multiplier to be 100, * for a permill, set the multiplier to be 1000.

* * Constraints: The subject is of type Float. * * @param floatSubject a Float ValueModel * @param multiplier the multiplier used to convert the Float to Integer * * @return a ValueModel that converts Floats to Integer * * @throws NullPointerException if the subject is {@code null} */ public static ValueModel createFloatToIntegerConverter( ValueModel floatSubject, int multiplier) { return new FloatToIntegerConverter(floatSubject, multiplier); } /** * Creates and returns a ValueModel that converts Integers using the * specified multiplier.

* * Examples: multiplier=100, Integer(3) -> Integer(300), * multiplier=1000, Integer(3) -> Integer(3000)

* * Constraints: The subject is of type Integer. * * @param integerSubject a Integer ValueModel * @param multiplier the multiplier used for the conversion * * @return a ValueModel that converts Integers using the specified multiplier * * @throws NullPointerException if the subject is {@code null} * * @since 1.0.2 */ public static ValueModel createIntegerConverter( ValueModel integerSubject, double multiplier) { return new IntegerConverter(integerSubject, multiplier); } /** * Creates and returns a ValueModel that converts Long using the * specified multiplier.

* * Examples: multiplier=100, Long(3) -> Long(300), * multiplier=1000, Long(3) -> Long(3000)

* * Constraints: The subject is of type Long. * * @param longSubject a Long ValueModel * @param multiplier the multiplier used for the conversion * * @return a ValueModel that converts Longs using the specified multiplier * * @throws NullPointerException if the subject is {@code null} * * @since 1.0.2 */ public static ValueModel createLongConverter( ValueModel longSubject, double multiplier) { return new LongConverter(longSubject, multiplier); } /** * Creates and returns a ValueModel that converts Longs to Integer * and vice versa.

* * Constraints: The subject is of type Long, * values written to the converter are of type Integer. * * @param longSubject a Long ValueModel * @return a ValueModel that converts Longs to Integer * * @throws NullPointerException if the subject is {@code null} */ public static ValueModel createLongToIntegerConverter(ValueModel longSubject) { return createLongToIntegerConverter(longSubject, 1); } /** * Creates and returns a ValueModel that converts Longs to Integer * and vice versa.

* * Constraints: The subject is of type Long, * values written to the converter are of type Integer. * * @param longSubject a Long ValueModel * @param multiplier used to multiply the Long when converting to Integer * @return a ValueModel that converts Longs to Integer * * @throws NullPointerException if the subject is {@code null} */ public static ValueModel createLongToIntegerConverter( ValueModel longSubject, int multiplier) { return new LongToIntegerConverter(longSubject, multiplier); } /** * Creates and returns a ValueModel that converts objects to Strings * and vice versa. The conversion is performed by a Format.

* * Constraints: The subject is of type Object; * it must be formattable and parsable via the given Format. * * @param subject the underlying ValueModel. * @param format the Format used to format and parse * * @return a ValueModel that converts objects to Strings and vice versa * * @throws NullPointerException if the subject or the format * is {@code null} */ public static ValueModel createStringConverter(ValueModel subject, Format format) { return new StringConverter(subject, format); } // Converter Implementations ********************************************** /** * Negates Booleans leaving null unchanged. Maps Boolean.TRUE * to Boolean.FALSE, Boolean.FALSE to Boolean.TRUE, and null to null. */ public static final class BooleanNegator extends AbstractConverter { BooleanNegator(ValueModel booleanSubject) { super(booleanSubject); } /** * Negates Booleans leaving null unchanged. * Maps Boolean.TRUE to Boolean.FALSE, * Boolean.FALSE to Boolean.TRUE, and null to null. * * @param subjectValue the subject value to invert * @return the text that represents the subject value * * @throws ClassCastException if the subject's value is not a Boolean */ @Override public Object convertFromSubject(Object subjectValue) { return negate(subjectValue); } /** * Inverts the given Boolean and sets it as the subject's new value. * * @param newValue the value to be inverted and set as new subject value * @throws ClassCastException if the new value is not a Boolean * @throws IllegalArgumentException if the new value does neither match * the trueText nor the falseText */ public void setValue(Object newValue) { subject.setValue(negate(newValue)); } /** * Negates Booleans leaving null unchanged. * Maps Boolean.TRUE to Boolean.FALSE , * Boolean.FALSE to Boolean.TRUE, and null to null. * * @param value the value to invert * @return the inverted Boolean value, or null if value is null * * @throws ClassCastException if the value is not a Boolean */ private Boolean negate(Object value) { if (value == null) { return null; } else if (Boolean.TRUE.equals(value)) { return Boolean.FALSE; } else if (Boolean.FALSE.equals(value)) { return Boolean.TRUE; } else { throw new ClassCastException("The value must be a Boolean."); } } } /** * Converts Booleans to Strings and vice-versa using given texts for * true, false, and null. Throws a ClassCastException if the value * to convert is not a Boolean, or not a String for the reverse conversion. */ public static final class BooleanToStringConverter extends AbstractConverter { private final String trueText; private final String falseText; private final String nullText; BooleanToStringConverter( ValueModel booleanSubject, String trueText, String falseText, String nullText) { super(booleanSubject); this.trueText = checkNotNull(trueText, "The trueText must not be null."); this.falseText = checkNotNull(falseText, "The falseText must not be null."); this.nullText = checkNotNull(nullText, "The nullText must not be null."); checkArgument(!trueText.equals(falseText), "The trueText and falseText must be different."); } /** * Converts the subject value to associated text representation. * Rejects non-Boolean values. * * @param subjectValue the subject's new value * @return the text that represents the subject value * * @throws ClassCastException if the subject's value is not a Boolean */ @Override public Object convertFromSubject(Object subjectValue) { if (Boolean.TRUE.equals(subjectValue)) { return trueText; } else if (Boolean.FALSE.equals(subjectValue)) { return falseText; } else if (subjectValue == null) { return nullText; } else { throw new ClassCastException( "The subject value must be of type Boolean."); } } /** * Converts the given String and sets the associated Boolean as * the subject's new value. In case the new value equals neither * this class' trueText, nor the falseText, nor the nullText, * an IllegalArgumentException is thrown. * * @param newValue the value to be converted and set as new subject value * @throws ClassCastException if the new value is not a String * @throws IllegalArgumentException if the new value does neither match * the trueText nor the falseText nor the nullText */ public void setValue(Object newValue) { if (!(newValue instanceof String)) { throw new ClassCastException( "The new value must be a string."); } String newString = (String) newValue; if (trueText.equalsIgnoreCase(newString)) { subject.setValue(Boolean.TRUE); } else if (falseText.equalsIgnoreCase(newString)) { subject.setValue(Boolean.FALSE); } else if (nullText.equalsIgnoreCase(newString)) { subject.setValue(null); } else { throw new IllegalArgumentException( "The new value must be one of: " + trueText + '/' + falseText + '/' + nullText); } } } /** * Converts Doubles using a given multiplier. */ public static final class DoubleConverter extends AbstractConverter { private final double multiplier; DoubleConverter( ValueModel doubleSubject, double multiplier) { super(doubleSubject); this.multiplier = multiplier; } /** * Converts the subject's value and returns a * corresponding Double using the multiplier. * * @param subjectValue the subject's value * @return the converted subjectValue * @throws ClassCastException if the subject value is not of type * Double */ @Override public Object convertFromSubject(Object subjectValue) { double doubleValue = ((Double) subjectValue).doubleValue(); return Double.valueOf(doubleValue * multiplier); } /** * Converts a Double using the multiplier * and sets it as new value. * * @param newValue the Double object that shall be converted * @throws ClassCastException if the new value is not of type * Double */ public void setValue(Object newValue) { double doubleValue = ((Double) newValue).doubleValue(); subject.setValue(Double.valueOf(doubleValue / multiplier)); } } /** * Converts Doubles to Integers and vice-versa. */ public static final class DoubleToIntegerConverter extends AbstractConverter { private final int multiplier; DoubleToIntegerConverter( ValueModel doubleSubject, int multiplier) { super(doubleSubject); this.multiplier = multiplier; } /** * Converts the subject's value and returns a * corresponding Integer value using the multiplier. * * @param subjectValue the subject's value * @return the converted subjectValue * @throws ClassCastException if the subject value is not of type * Double */ @Override public Object convertFromSubject(Object subjectValue) { double doubleValue = ((Double) subjectValue).doubleValue(); if (multiplier != 1) { doubleValue *= multiplier; } return Integer.valueOf((int) Math.round(doubleValue)); } /** * Converts a Double using the multiplier * and sets it as new value. * * @param newValue the Integer object that shall * be converted * @throws ClassCastException if the new value is not of type * Integer */ public void setValue(Object newValue) { double doubleValue = ((Integer) newValue).doubleValue(); if (multiplier != 1) { doubleValue /= multiplier; } subject.setValue(Double.valueOf(doubleValue)); } } /** * Converts Floats using a given multiplier. */ public static final class FloatConverter extends AbstractConverter { private final float multiplier; FloatConverter( ValueModel floatSubject, float multiplier) { super(floatSubject); this.multiplier = multiplier; } /** * Converts the subject's value and returns a * corresponding Float using the multiplier. * * @param subjectValue the subject's value * @return the converted subjectValue * @throws ClassCastException if the subject value is not of type * Float */ @Override public Object convertFromSubject(Object subjectValue) { float floatValue = ((Float) subjectValue).floatValue(); return Float.valueOf(floatValue * multiplier); } /** * Converts a Float using the multiplier * and sets it as new value. * * @param newValue the Float object that shall be converted * @throws ClassCastException if the new value is not of type * Float */ public void setValue(Object newValue) { float floatValue = ((Float) newValue).floatValue(); subject.setValue(Float.valueOf(floatValue / multiplier)); } } /** * Converts Floats to Integers and vice-versa. */ public static final class FloatToIntegerConverter extends AbstractConverter { private final int multiplier; FloatToIntegerConverter( ValueModel floatSubject, int multiplier) { super(floatSubject); this.multiplier = multiplier; } /** * Converts the subject's value and returns a * corresponding Integer using the multiplier. * * @param subjectValue the subject's value * @return the converted subjectValue * @throws ClassCastException if the subject value is not of type * Float */ @Override public Object convertFromSubject(Object subjectValue) { float floatValue = ((Float) subjectValue).floatValue(); if (multiplier != 1) { floatValue *= multiplier; } return Integer.valueOf(Math.round(floatValue)); } /** * Converts a Float using the multiplier and * sets it as new value. * * @param newValue the Integer object that shall be converted * @throws ClassCastException if the new value is not of type * Integer */ public void setValue(Object newValue) { float floatValue = ((Integer) newValue).floatValue(); if (multiplier != 1) { floatValue /= multiplier; } subject.setValue(Float.valueOf(floatValue)); } } /** * Converts Longs using a given multiplier. */ public static final class LongConverter extends AbstractConverter { private final double multiplier; LongConverter( ValueModel longSubject, double multiplier) { super(longSubject); this.multiplier = multiplier; } /** * Converts the subject's value and returns a * corresponding Long using the multiplier. * * @param subjectValue the subject's value * @return the converted subjectValue * @throws ClassCastException if the subject value is not of type * Long */ @Override public Object convertFromSubject(Object subjectValue) { double doubleValue = ((Long) subjectValue).doubleValue(); return Long.valueOf((long) (doubleValue * multiplier)); } /** * Converts a Long using the multiplier * and sets it as new value. * * @param newValue the Long object that shall be converted * @throws ClassCastException if the new value is not of type * Long */ public void setValue(Object newValue) { double doubleValue = ((Long) newValue).doubleValue(); subject.setValue(Long.valueOf((long) (doubleValue / multiplier))); } } /** * Converts Integers using a given multiplier. */ public static final class IntegerConverter extends AbstractConverter { private final double multiplier; IntegerConverter( ValueModel integerSubject, double multiplier) { super(integerSubject); this.multiplier = multiplier; } /** * Converts the subject's value and returns a * corresponding Integer using the multiplier. * * @param subjectValue the subject's value * @return the converted subjectValue * @throws ClassCastException if the subject value is not of type * Integer */ @Override public Object convertFromSubject(Object subjectValue) { double doubleValue = ((Integer) subjectValue).doubleValue(); return Integer.valueOf((int) (doubleValue * multiplier)); } /** * Converts a Integer using the multiplier * and sets it as new value. * * @param newValue the Integer object that shall be converted * @throws ClassCastException if the new value is not of type * Integer */ public void setValue(Object newValue) { double doubleValue = ((Integer) newValue).doubleValue(); subject.setValue(Integer.valueOf((int) (doubleValue / multiplier))); } } /** * Converts Longs to Integers and vice-versa. */ public static final class LongToIntegerConverter extends AbstractConverter { private final int multiplier; LongToIntegerConverter( ValueModel longSubject, int multiplier) { super(longSubject); this.multiplier = multiplier; } /** * Converts the subject's value and returns a * corresponding Integer. * * @param subjectValue the subject's value * @return the converted subjectValue * @throws ClassCastException if the subject value is not of type * Float */ @Override public Object convertFromSubject(Object subjectValue) { int intValue = ((Long) subjectValue).intValue(); if (multiplier != 1) { intValue *= multiplier; } return Integer.valueOf(intValue); } /** * Converts an Integer to Long and sets it as new value. * * @param newValue the Integer object that represents * the percent value * @throws ClassCastException if the new value is not of type * Integer */ public void setValue(Object newValue) { long longValue = ((Integer) newValue).longValue(); if (multiplier != 1) { longValue /= multiplier; } subject.setValue(Long.valueOf(longValue)); } } /** * Converts Values to Strings and vice-versa using a given Format. */ public static final class StringConverter extends AbstractConverter { /** * Holds the Format used to format and parse. */ private final Format format; // Instance Creation ************************************************** /** * Constructs a StringConverter on the given * subject using the specified Format. * * @param subject the underlying ValueModel. * @param format the Format used to format and parse * @throws NullPointerException if the subject or the format is null. */ StringConverter(ValueModel subject, Format format) { super(subject); this.format = checkNotNull(format, "The format must not be null."); } // Implementing Abstract Behavior ************************************* /** * Formats the subject value and returns a String representation. * * @param subjectValue the subject's value * @return the formatted subjectValue */ @Override public Object convertFromSubject(Object subjectValue) { return format.format(subjectValue); } // Implementing ValueModel ******************************************** /** * Parses the given String encoding and sets it as the subject's * new value. Silently catches ParseException. * * @param value the value to be converted and set as new subject value */ public void setValue(Object value) { try { subject.setValue(format.parseObject((String) value)); } catch (ParseException e) { // Do not change the subject value } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/value/ValueHolder.java0000644000175000017500000002130311374522114026565 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.value; /** * A simple {@link com.jgoodies.binding.value.ValueModel} implementation * that holds a generic value. * If the value changes, a PropertyChangeEvent is fired * that can be observed using a PropertyChangeListener.

* * Differences in the old and new value can be checked either using * == or #equals. The unbound property * identityCheckEnabled determines which mechanism is used * to check for changes in #setValue(Object). * This check can be overridden for individual changes by the boolean * parameter in #setValue(Object, boolean).

* * Constraints: The value is of type Object. * * @author Karsten Lentzsch * @version $Revision: 1.9 $ * * @see ValueModel * @see java.beans.PropertyChangeEvent * @see java.beans.PropertyChangeListener * @see com.jgoodies.binding.beans.ExtendedPropertyChangeSupport */ public final class ValueHolder extends AbstractValueModel { /** * Holds a value of type Object that is to be observed. */ private Object value; /** * Describes whether a value change event shall be fired if the * old and new value are different. If {@code true} the old * and new value are compared with ==. If {@code false} * the values are compared with #equals. * * @see #setValue(Object, boolean) * @see com.jgoodies.binding.beans.Model#firePropertyChange(String, Object, Object, boolean) * @see com.jgoodies.binding.beans.ExtendedPropertyChangeSupport */ private boolean checkIdentity; // Instance Creation **************************************************** /** * Constructs a ValueHolder with {@code null} * as initial value. */ public ValueHolder() { this(null); } /** * Constructs a ValueHolder with the given initial value. * By default the old and new value are compared using #equals * when firing value change events. * * @param initialValue the initial value */ public ValueHolder(Object initialValue) { this(initialValue, false); } /** * Constructs a ValueHolder with the given initial value. * * @param initialValue the initial value * @param checkIdentity true to compare the old and new value using * ==, false to use #equals */ public ValueHolder(Object initialValue, boolean checkIdentity) { value = initialValue; this.checkIdentity = checkIdentity; } /** * Constructs a ValueHolder with the specified initial * boolean value that is converted to a Boolean object. * * @param initialValue the initial boolean value */ public ValueHolder(boolean initialValue) { this(Boolean.valueOf(initialValue)); } /** * Constructs a ValueHolder with the specified initial * double value that is converted to a Double object. * * @param initialValue the initial double value */ public ValueHolder(double initialValue) { this(Double.valueOf(initialValue)); } /** * Constructs a ValueHolder with the specified initial * float value that is converted to a Float object. * * @param initialValue the initial float value */ public ValueHolder(float initialValue) { this(Float.valueOf(initialValue)); } /** * Constructs a ValueHolder with the specified initial * int value that is converted to an Integer object. * * @param initialValue the initial int value */ public ValueHolder(int initialValue) { this(Integer.valueOf(initialValue)); } /** * Constructs a ValueHolder with the specified initial * long value that is converted to a Long object. * * @param initialValue the initial long value */ public ValueHolder(long initialValue) { this(Long.valueOf(initialValue)); } // ValueModel Implementation ******************************************** /** * Returns the observed value. * * @return the observed value */ public Object getValue() { return value; } /** * Sets a new value. Fires a value change event if the old and new * value differ. The difference is tested with == if * isIdentityCheckEnabled answers {@code true}. * The values are compared with #equals if the * identity check is disabled. * * @param newValue the new value */ public void setValue(Object newValue) { setValue(newValue, isIdentityCheckEnabled()); } // Optional Support for Firing Events on Identity Change *************** /** * Answers whether this ValueHolder fires value change events if * and only if the old and new value are not the same. * * @return {@code true} if the old and new value are compared * using ==, {@code false} if the values * are compared using #equals */ public boolean isIdentityCheckEnabled() { return checkIdentity; } /** * Sets the comparison that is used to check differences between * the old and new value when firing value change events. * This is the default setting that is used when changing the value via * #setValue(Object). You can override this default setting * by changing a value via #setValue(Object, boolean). * * @param checkIdentity true to compare the old and new value using * ==, false to use #equals */ public void setIdentityCheckEnabled(boolean checkIdentity) { this.checkIdentity = checkIdentity; } /** * Sets a new value. Fires a value change event if the old and new * value differ. The difference is tested with == if * checkIdentity is {@code true}. The values are * compared with #equals if the checkIdentiy * parameter is set to {@code false}.

* * Unlike general bean property setters, this method does not fire * an event if the old and new value are {@code null}. * * @param newValue the new value * @param checkIdentity true to compare the old and new value using * ==, false to use #equals */ public void setValue(Object newValue, boolean checkIdentity) { Object oldValue = getValue(); if (oldValue == newValue) return; value = newValue; fireValueChange(oldValue, newValue, checkIdentity); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/package.html0000644000175000017500000000517111374522114024662 0ustar twernertwerner Contains utilities and the PresentationModel that combines many of the Binding features.

Related Documentation

For more information see: @see com.jgoodies.binding.adapter @see com.jgoodies.binding.beans @see com.jgoodies.binding.formatter @see com.jgoodies.binding.list @see com.jgoodies.binding.value jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/PresentationModel.java0000644000175000017500000021367011374522114026705 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.util.HashMap; import java.util.Map; import javax.swing.JComponent; import com.jgoodies.binding.adapter.Bindings; import com.jgoodies.binding.beans.*; import com.jgoodies.binding.value.*; import com.jgoodies.common.base.Objects; /** * The standard base class to implement the Presentation Model pattern, * that represents the state and behavior of a presentation independently * of the GUI components used in the interface. This * pattern * is described in Martin Fowler's upcoming * addition * to his "Patterns of Enterprise Application Architecture". More details * around this implementation of the Presentation Model pattern and a 3-tier * Swing client architecture with a presentation model layer can be found in * the JGoodies * Binding presentation. This architecture is supported * by the JGoodies Binding library.

* * This class minimizes the effort required to bind, edit, * buffer, and observe the bound properties of an exchangeable bean. * Therefore it provides five groups of features that are described below:

    *
  1. adapt bean properties, *
  2. change the adapted bean, *
  3. buffer values, *
  4. observe the buffering state, and *
  5. track changes in adapted bean properties. *

* * Typically this class will be extended to add custom models, Actions, * presentation logic, model operations and other higher-level behavior. * However, in simple cases you can use this class as-is. * Several methods are intended to be used as-is and a typical subclass * should not modify them. For example #isChanged, #isBuffering, * #getBean, #setBean, #getBeanChannel, #getModel, #getBufferedModel, * #getTriggerChannel, #setTriggerChannel, #triggerCommit and #triggerFlush.

* * Adapting Bean Properties
* The method {@link #getModel(String)} vends ValueModels that adapt * a bound bean property of an exchangeable bean. These ValueModels will be * requested from an underlying BeanAdapter. * To get such a model you specify the name of the bean property. * All properties adapted must be read-write and must comply with * the Java Bean coding conventions. * In case you need to adapt a read-only or write-only property, * or if the bean uses custom names for the reader and writer, * use {@link #getModel(String, String, String)}. * Also note that you must not mix calls to these methods for the same * property name. For details see the JavaDoc class comment in * {@link com.jgoodies.binding.beans.BeanAdapter}.

* * Changing the Adapted Bean
* The adapted bean is not stored in this PresentationModel. * Instead it is held by a ValueModel, the bean channel * - just as in the PropertyAdapter and BeanAdapter. * This indirection enables you to manage the adapted bean outside * of this PresentationModel, and it enables you to share bean channels * between multiple PresentationModels, PropertyAdapters, and BeanAdapters. * The bean channel is used by all adapting models created * by the factory methods #getModel. * You can get and set the current bean by means of #getBean * and #setBean. Or you can set a new value to the bean channel.

* * PresentationModel fires three PropertyChangeEvents if the bean changes: * beforeBean, bean and afterBean. This is useful * when sharing a bean channel and you must perform an operation before * or after other listeners handle a bean change. Since you cannot rely * on the order listeners will be notified, only the beforeBean * and afterBean events are guaranteed to be fired before and * after the bean change is fired. * Note that #getBean() returns the new bean before * any of these three PropertyChangeEvents is fired. Therefore listeners * that handle these events must use the event's old and new value * to determine the old and new bean. * The order of events fired during a bean change is:

    *
  1. the bean channel fires a value change, *
  2. this model fires a beforeBean change, *
  3. this model fires the bean change, *
  4. this model fires an afterBean change. *
* * Buffering Values
* At the core of this feature are the methods {@link #getBufferedModel(String)} * that vend BufferedValueModels that wrap an adapted bean property. * The buffer can be committed or flushed using #triggerCommit * and #triggerFlush respectively.

* * The trigger channel is provided as a bound Java bean property * triggerChannel that must be a non-{@code null} * ValueModel with values of type Boolean. * Attempts to read or write other value types may be rejected * with runtime exceptions. * By default the trigger channel is initialized as an instance of * Trigger. As an alternative it can be set in the constructor.

* * Observing the Buffering State
* This class also provides support for observing the buffering state * of the BufferedValueModels created with this model. The buffering state * is useful for UI actions and operations that are enabled or disabled * if there are pending changes, for example on OK or APPLY button. * API users can request the buffering state via #isBuffering * and can observe the bound property buffering.

* * Tracking Changes in the Adapted Bean
* PresentationModel provides support for observing bean property changes * and it tracks all changes to report the overall changed state. * The latter is useful to detect whether the bean has changed at all, * for example to mark the bean as dirty, so it will be updated in a database. * API users can request the changed state via #isChanged * and can observe the bound property changed. * If you want to track changes of other ValueModels, bean properties, * or of submodels, register them using #observeChanged. * To reset the changed state invoke #resetChanged. * In case you track the changed state of submodels you should override * #resetChanged to reset the changed state in these submodels.

* * The changed state changes once only (from false to true). If you need * instant notifications about changes in the properties of the target bean, * you can register PropertyChangeListeners with this model. This is useful * if you change the bean and don't want to move your listeners from one bean * to the other. And it's useful if you want to observe multiple bean * properties at the same time. These listeners are managed by the method set * #addBeanPropertyChangeListener and * #removeBeanPropertyChangeListener. * Listeners registered via these methods will be removed * from the old bean before the bean changes and will be re-added after * the new bean has been set. Therefore these listeners will be notified * about changes only if the current bean changes a property. They won't be * notified if the bean changes - and in turn the property value. If you want * to observes property changes caused by bean changes too, register with * the adapting ValueModel as returned by #getModel(String).

* * Instance Creation
* PresentationModel can be instantiated using four different constructors: * you can specify the target bean directly, or you can provide a * bean channel to access the bean indirectly. * In the latter case you specify a ValueModel * that holds the bean that in turn holds the adapted property. * In both cases the target bean is accessed indirectly through * the bean channel. In both cases you can specify a custom trigger channel, * or you can use a default trigger channel.

* * Note: This PresentationModel provides bound bean properties * and you can register and unregister PropertyChangeListers as usual using * #addPropertyChangeListener and * #removePropertyChangeListener. Do not mix up * the model listeners with the listeners registered with the bean.

* * Warning: PresentationModels register a * PropertyChangeListener with the target bean. Hence, a bean has a reference * to all PresentationModels that hold it as target bean. To avoid memory leaks * it is recommended to remove this listener if the bean lives much longer * than the PresentationModel, enabling the garbage collector to remove * the PresentationModel. * Setting a PresentationModel's target bean to null removes this listener, * which in turn clears the reference from the bean to the PresentationModel. * To do so, you can call setBean(null) or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with WeakReference. * Setting the bean to null has side effects, which is fine in most cases. * However, you can release all listeners by calling #release.

* * TODO: Further improve the class comment.

* * TODO: Consider adding a feature to ensure that update notifications * are performed in the event dispatch thread. In case the adapted bean * is changed in a thread other than the event dispatch thread, such * a feature would help complying with Swing's single thread rule. * The feature could be implemented by an extended PropertyChangeSupport.

* * TODO: I plan to improve the support for adapting beans that do not fire * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter, * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's * internal SimplePropertyAdapter's shall be able to optionally self-fire * a PropertyChangeEvent in case the bean does not. There are several * downsides with self-firing events compared to bound bean properties. * See Issue * 49 for more information about the downsides.

* * The observeChanges constructor parameter shall be replaced by a more * fine-grained choice to not observe (former observeChanges=false), * to observe bound properties (former observeChanges=true), and a new * setting for self-firing PropertyChangeEvents if a value is set. * The latter case may be further split up to specify how the * self-fired PropertyChangeEvent is created: *

    *
  1. oldValue=null, newValue=null *
  2. oldValue=null, newValue=the value set *
  3. oldValue=value read before the set, newValue=the value set *
  4. oldValue=value read before the set, newValue=value read after the set *
* * @author Karsten Lentzsch * @version $Revision: 1.25 $ * * @see com.jgoodies.binding.beans.BeanAdapter * @see com.jgoodies.binding.value.ValueModel * @see com.jgoodies.binding.beans.PropertyAdapter * @see com.jgoodies.binding.value.Trigger * * @param the type of the bean managed by this PresentationModel */ public class PresentationModel extends Model { /** * The property name used in the PropertyChangeEvent that is fired * before the bean property fires its PropertyChangeEvent. * Useful to perform an operation before listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean"; /** * The name of the read-write bound property that holds the target bean. * * @see #getBean() * @see #setBean(Object) */ public static final String PROPERTYNAME_BEAN = "bean"; /** * The property name used in the PropertyChangeEvent that is fired * after the bean property fires its PropertyChangeEvent. * Useful to perform an operation after listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTYNAME_AFTER_BEAN = "afterBean"; /** * The name of the read-write bound bean property for the * trigger channel that is shared by all PropertyAdapters * that are created via #getBufferedModel. * * @see #getTriggerChannel() * @see #setTriggerChannel(ValueModel) * @see #getBufferedModel(String) */ public static final String PROPERTYNAME_TRIGGERCHANNEL = "triggerChannel"; /** * The name of the read-only bound bean property that indicates * whether one of the buffered models is buffering. * * @see #isBuffering() * @see #getBufferedModel(String) */ public static final String PROPERTYNAME_BUFFERING = "buffering"; /** * The name of the read-only bound bean property that * indicates whether one of the observed models has changed. * * @see #isChanged() * @see #resetChanged() * @see #observeChanged(ValueModel) * @see #observeChanged(Object, String) */ public static final String PROPERTYNAME_CHANGED = "changed"; // Fields ***************************************************************** /** * Refers to the BeanAdapter that provides all underlying behavior * to vend adapting ValueModels, track bean changes, and to register * with bound bean properties. */ private final BeanAdapter beanAdapter; /** * Holds a three-state trigger channel that can be used to trigger * commit and reset events in instances of BufferedValueModel. * The trigger value is changed to true in #triggerCommit * and is changed to false in #triggerFlush.

* * The trigger channel is initialized as a Trigger * but may be replaced by any other ValueModel that accepts booleans. * * @see #getTriggerChannel() * @see #setTriggerChannel(ValueModel) * @see #getBufferedModel(String) */ private ValueModel triggerChannel; /** * Maps property names to instances of the inner class WrappedBuffer. * These hold a BufferedValueModel associated with the property name, * as well as an optional getter and setter name. These accessor names * are used to check that multiple calls to #getBufferedModel * use the same getter and setter for a given property name.

* * The indirectly stored BufferedValueModel are checked whenever * the buffering state is updated. And these model's trigger channel * is updated when the PresentationModel gets a new trigger channel. * * @see #getBufferedModel(String) * @see #getBufferedModel(String, String, String) * @see #isBuffering() * @see #setTriggerChannel(ValueModel) */ private final Map wrappedBuffers; /** * Listens to value changes and validates this model. * The validation result is available in the validationResultHolder.

* * Also listens to changes of the buffering property in * BufferedValueModels and updates the buffering state * - if necessary. */ private final PropertyChangeListener bufferingUpdateHandler; /** * Indicates whether a registered buffered model has a pending change, * in other words whether any of the values has been edited or not. */ private boolean buffering = false; /** * Listens to property changes and updates the changed property. */ private final PropertyChangeListener changedUpdateHandler; /** * Indicates whether a registered model has changed. */ private boolean changed = false; /** * Maps property names to instances of ComponentValueModel. * Used to ensure that multiple calls to #getComponentModel * return the same instance. * * @see #getComponentModel(String) */ private final Map componentModels; /** * Maps property names to instances of ComponentValueModel. * Used to ensure that multiple calls to #getBufferedComponentModel * return the same instance. * * @see #getBufferedComponentModel(String) */ private final Map bufferedComponentModels; // Instance Creation ****************************************************** /** * Constructs a PresentationModel that adapts properties of the given bean.

* * Installs a default bean channel that checks the identity not equity * to ensure that listeners are unregistered properly if the old and * new bean are equal but not the same.

* * Installs a Trigger as initial trigger channel. * * @param bean the bean that holds the properties to adapt * @throws PropertyUnboundException if the bean does not * provide a pair of methods to register a PropertyChangeListener */ public PresentationModel(B bean) { this(new ValueHolder(bean, true)); } /** * Constructs a PresentationModel on the given bean using the given * trigger channel. The bean provides the properties to adapt.

* * Installs a default bean channel that checks the identity not equity * to ensure that listeners are unregistered properly if the old and * new bean are equal but not the same.

* * The trigger channel is shared by all buffered models that are created * using #getBufferedModel. * It can be replaced by any other Boolean ValueModel later. * Note that PresentationModel observes trigger value changes, * not value state. Therefore you must ensure that customer triggers * report value changes when asked to commit or flush. See the * Trigger implementation for an example. * * @param bean the bean that holds the properties to adapt * @param triggerChannel the ValueModel that triggers commit and flush events */ public PresentationModel( B bean, ValueModel triggerChannel) { this(new ValueHolder(bean, true), triggerChannel); } /** * Constructs a PresentationModel on the given bean channel. This channel * holds a bean that in turn holds the properties to adapt.

* * It is strongly recommended that the bean channel checks the identity * not equity. This ensures that listeners are unregistered properly if * the old and new bean are equal but not the same.

* * The trigger channel is initialized as a Trigger. * It may be replaced by any other Boolean ValueModel later. * Note that PresentationModel observes trigger value changes, * not value state. Therefore you must ensure that customer triggers * report value changes when asked to commit or flush. See the * Trigger implementation for an example. * * @param beanChannel the ValueModel that holds the bean * * @throws PropertyUnboundException if the bean does not * provide a pair of methods to register a PropertyChangeListener */ public PresentationModel(ValueModel beanChannel) { this(beanChannel, new Trigger()); } /** * Constructs a PresentationModel on the given bean channel using the given * trigger channel. The bean channel holds a bean that in turn holds * the properties to adapt.

* * It is strongly recommended that the bean channel checks the identity * not equity. This ensures that listeners are unregistered properly if * the old and new bean are equal but not the same.

* * The trigger channel is shared by all buffered * models that are created using #buffer. * It can be replaced by any other Boolean ValueModel later. * Note that PresentationModel observes trigger value changes, * not value state. Therefore you must ensure that customer triggers * report value changes when asked to commit or flush. See the * Trigger implementation for an example. * * @param beanChannel the ValueModel that holds the bean * @param triggerChannel the ValueModel that triggers commit and flush events */ public PresentationModel( ValueModel beanChannel, ValueModel triggerChannel) { this.beanAdapter = createBeanAdapter(beanChannel); this.triggerChannel = triggerChannel; this.wrappedBuffers = new HashMap(); this.componentModels = new HashMap(); this.bufferedComponentModels = new HashMap(); this.bufferingUpdateHandler = new BufferingStateHandler(); this.changed = false; this.changedUpdateHandler = new UpdateHandler(); beanAdapter.addPropertyChangeListener(new BeanChangeHandler()); // By default we observe changes in the bean. observeChanged(beanAdapter, BeanAdapter.PROPERTYNAME_CHANGED); } /** * Creates and returns a BeanAdapter for the given bean channel. * For compatibility with the 1.0.x, 1.1.x, and 1.2.x series, * this default implementation creates a BeanAdapter that always observes * the bean. Subclasses may override to observe only observable beans.

* * Here's an example code for a custom implementation: *

     *  boolean observe =
     *        (beanChannel == null)
     *     || (beanChannel.getValue() == null)
     *     || BeanUtils.supportsBoundProperties((beanChannel.getValue().getClass());
     *  return new BeanAdapter(beanChannel, observe);
     *  

* * A future implementation shall return a BeanAdapter-like interface, * not a BeanAdapter. * * @param beanChannel the ValueModel that holds the bean * @return the created bean adapter * @since 1.3 */ protected BeanAdapter createBeanAdapter(ValueModel beanChannel) { return new BeanAdapter(beanChannel, true); } // Managing the Target Bean ********************************************** /** * Returns the ValueModel that holds the bean that in turn holds * the adapted properties. This bean channel is shared by the * PropertyAdapters created by the factory methods * #getModel and #getBufferedModel. * * @return the ValueModel that holds the bean that in turn * holds the adapted properties * * @see #getBean() * @see #setBean(Object) */ public ValueModel getBeanChannel() { return beanAdapter.getBeanChannel(); } /** * Returns the bean that holds the adapted properties. This bean * is the bean channel's content. * * @return the bean that holds the adapted properties * * @see #setBean(Object) * @see #getBeanChannel() */ public B getBean() { return (B) getBeanChannel().getValue(); } /** * Sets a new bean as content of the bean channel. * All adapted properties will reflect this change. * * @param newBean the new bean * * @see #getBean() * @see #getBeanChannel() */ public void setBean(B newBean) { getBeanChannel().setValue(newBean); } /** * The underlying BeanAdapter is about to change the bean. * Allows to perform actions before the bean change happens. * For example you can remove listeners that shall not be notified * if adapted properties change just because of the bean change. * Or you can reset values, set fields to {@code null} etc.

* * The default behavior fires a PropertyChangeEvent for property * #PROPERTYNAME_BEFORE_BEAN. * Note: Subclasses that override this method * must invoke super or perform the same behavior.

* * This method is invoked by the BeanChangeHandler listening to the * beforeBean non-readable property of the BeanAdapter. * * @param oldBean the bean before the change * @param newBean the bean that will be adapted after the change * * @see #afterBeanChange(Object, Object) * @see #PROPERTYNAME_BEFORE_BEAN * @see #PROPERTYNAME_BEAN * @see #PROPERTYNAME_AFTER_BEAN * @see BeanAdapter */ public void beforeBeanChange(B oldBean, B newBean) { firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean, true); } /** * The underlying BeanAdapter has changed the target bean. * Allows to perform actions after the bean changed. * For example you can re-add listeners that were removed in * #beforeBeanChange. Or you can reset values, * reset custom changed state, set fields to {@code null} etc.

* * The default behavior resets the change tracker's changed state * and fires a PropertyChangeEvent for the property * #PROPERTYNAME_AFTER_BEAN. * Note: Subclasses that override this method * must invoke super or perform the same behavior.

* * This method is invoked by the BeanChangeHandler listening to the * afterBean non-readable property of the BeanAdapter. * * @param oldBean the bean that was adapted before the change * @param newBean the bean that is already the new target bean * * @see #beforeBeanChange(Object, Object) * @see #PROPERTYNAME_BEFORE_BEAN * @see #PROPERTYNAME_BEAN * @see #PROPERTYNAME_AFTER_BEAN * @see BeanAdapter */ public void afterBeanChange(B oldBean, B newBean) { setChanged(false); firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean, true); } // Accessing Property Values ********************************************** /** * Returns the value of specified bean property, {@code null} * if the current bean is {@code null}.

* * This operation is supported only for readable bean properties. * * @param propertyName the name of the property to be read * @return the value of the adapted bean property, null if the bean is null * * @throws NullPointerException if the property name is null * @throws UnsupportedOperationException if the property is write-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the value could not be read * * @since 1.1 */ public Object getValue(String propertyName) { return beanAdapter.getValue(propertyName); } /** * Sets the given new value for the specified bean property. Does nothing * if this adapter's bean is {@code null}. If the setter associated * with the propertyName throws a PropertyVetoException, it is silently * ignored.

* * Notifies the associated value change listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal.

* * This operation is supported only for writable bean properties. * * @param propertyName the name of the property to set * @param newValue the value to set * * @throws NullPointerException if the property name is null * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set * * @since 1.1 */ public void setValue(String propertyName, Object newValue) { beanAdapter.setValue(propertyName, newValue); } /** * Sets a new value for the specified bean property. Does nothing if the * bean is {@code null}. If the setter associated with the propertyName * throws a PropertyVetoException, this methods throws the same exception.

* * Notifies the associated value change listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal.

* * This operation is supported only for writable bean properties. * * @param propertyName the name of the property to set * @param newValue the value to set * * @throws NullPointerException if the property name is null * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set * @throws PropertyVetoException if the bean setter * throws a PropertyVetoException * * @since 1.1 */ public void setVetoableValue(String propertyName, Object newValue) throws PropertyVetoException { beanAdapter.setVetoableValue(propertyName, newValue); } /** * Returns the value of specified buffered bean property. * It is a shorthand for writing *

getBufferedModel(propertyName).getValue()
* As a side-effect, this method may create a buffered model. * * @param propertyName the name of the property to be read * @return the value of the adapted bean property, null if the bean is null * * @throws NullPointerException if the property name is null * @throws UnsupportedOperationException if the property is write-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the value could not be read * * @since 1.1 */ public Object getBufferedValue(String propertyName) { return getBufferedModel(propertyName).getValue(); } /** * Buffers the given value for the specified bean property. * It is a shorthand for writing *
getBufferedModel(propertyName).setValue(newValue)
* As a side-effect, this method may create a buffered model. * * @param propertyName the name of the property to set * @param newValue the value to set * * @throws NullPointerException if the property name is null * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set * * @since 1.1 */ public void setBufferedValue(String propertyName, Object newValue) { getBufferedModel(propertyName).setValue(newValue); } // Factory Methods for Bound Models *************************************** /** * Looks up and lazily creates a ValueModel that adapts * the bound property with the specified name. Uses the * Bean introspection to look up the getter and setter names.

* * Subsequent calls to this method with the same property name * return the same ValueModel.

* * To prevent potential runtime errors it eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method * and to #getModel(String, String, String) must use * the same getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially once * you've called this method you must not call * #getModel(String, String, String) with a non-null * getter or setter name. And vice versa, once you've called the latter * method with a non-null getter or setter name, you must not call * this method.

* * This method uses a return type of AbstractValueModel, not a ValueModel. * This makes the AbstractValueModel convenience type converters available, * which can significantly shrink the source code necessary to read and * write values from/to these models. * * @param propertyName the name of the property to adapt * @return a ValueModel that adapts the property with the specified name * * @throws NullPointerException if the property name is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException * if #getModel(String, String, String) has been * called before with the same property name and a non-null getter * or setter name * * @see AbstractValueModel * @see BeanAdapter * @see #getModel(String, String, String) * @see #getBufferedModel(String) */ public AbstractValueModel getModel(String propertyName) { return beanAdapter.getValueModel(propertyName); } /** * Looks up and lazily creates a ValueModel that adapts the bound property * with the given name. Unlike #getModel(String) * this method bypasses the Bean Introspection and uses the given getter * and setter names to setup the access to the adapted Bean property.

* * Subsequent calls to this method with the same parameters * will return the same ValueModel.

* * To prevent potential runtime errors this method eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method * and to #getModel(String) must use the same * getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially * once you've called this method with a non-null getter or setter name, * you must not call #getModel(String). And vice versa, * once you've called the latter method you must not call this method * with a non-null getter or setter name.

* * This method uses a return type of AbstractValueModel, not a ValueModel. * This makes the AbstractValueModel convenience type converters available, * which can significantly shrink the source code necessary to read and * write values from/to these models. * * @param propertyName the name of the property to adapt * @param getterName the name of the method that reads the value * @param setterName the name of the method that sets the value * @return a ValueModel that adapts the property with the specified name * * @throws NullPointerException if the property name is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException if this method has been called before * with the same property name and different getter or setter names * * @see AbstractValueModel * @see BeanAdapter * @see #getModel(String, String, String) * @see #getBufferedModel(String) */ public AbstractValueModel getModel(String propertyName, String getterName, String setterName) { return beanAdapter.getValueModel(propertyName, getterName, setterName); } /** * Looks up and lazily creates a ComponentValueModel that adapts * the bound property with the specified name. Uses the standard * Bean introspection to look up the getter and setter names.

* * Subsequent calls to this method with the same property name * return the same ComponentValueModel.

* * To prevent potential runtime errors it eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method * and to #getModel(String, String, String) must use * the same getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially once * you've called this method you must not call * #getModel(String, String, String) with a non-null * getter or setter name. And vice versa, once you've called the latter * method with a non-null getter or setter name, you must not call * this method.

* * This returned ComponentValueModel provides convenience type converter * method from AbstractValueModel and allows to modify GUI state such as * enabled, visible, and editable in this presentation model. * This can significantly shrink the source code necessary to handle * GUI state changes. * * @param propertyName the name of the property to adapt * @return a ValueModel that adapts the property with the specified name * * @throws NullPointerException if the property name is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException * if #getModel(String, String, String) has been * called before with the same property name and a non-null getter * or setter name * * @see ComponentValueModel * @see AbstractValueModel * @see BeanAdapter * @see #getModel(String, String, String) * @see #getBufferedModel(String) * @see Bindings#addComponentPropertyHandler(JComponent, ValueModel) * * @since 1.1 */ public ComponentValueModel getComponentModel(String propertyName) { ComponentValueModel componentModel = componentModels.get(propertyName); if (componentModel == null) { AbstractValueModel model = getModel(propertyName); componentModel = new ComponentValueModel(model); componentModels.put(propertyName, componentModel); } return componentModel; } // Factory Methods for Buffered Models ************************************ /** * Looks up or creates a buffered adapter to the read-write property * with the given name on this PresentationModel's bean channel. Creates a * BufferedValueModel that wraps a ValueModel that adapts the bean property * with the specified name. The buffered model uses this PresentationModel's * trigger channel to listen for commit and flush events.

* * The created BufferedValueModel is stored in a Map. Hence * subsequent calls to this method with the same property name * return the same BufferedValueModel.

* * To prevent potential runtime errors this method eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method * and to #getBufferedModel(String, String, String) must use * the same getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially once * you've called this method you must not call * #getBufferedModel(String, String, String) with a non-null * getter or setter name. And vice versa, once you've called the latter * method with a non-null getter or setter name, you must not call * this method. * * @param propertyName the name of the read-write property to adapt * @return a buffered adapter to the property with the given name * on this model's bean channel using this model's trigger channel * * @throws NullPointerException if the property name is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException * if #getBufferedModel(String, String, String) has been * called before with the same property name and a non-null getter * or setter name * * @see BufferedValueModel * @see ValueModel * @see Trigger * @see BeanAdapter * @see #getModel(String) * @see #getBufferedModel(String, String, String) */ public BufferedValueModel getBufferedModel(String propertyName) { return getBufferedModel(propertyName, null, null); } /** * Looks up or creates a buffered adapter to the read-write property * with the given name on this PresentationModel's bean channel using * the specified getter and setter name to read and write values. Creates * a BufferedValueModel that wraps a ValueModel * that adapts the bean property with the specified name. * The buffered model uses this PresentationModel's trigger channel * to listen for commit and flush events.

* * The created BufferedValueModel is stored in a Map so it can be * looked up if it is requested multiple times.

* * To prevent potential runtime errors this method eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method * and to #getBufferedModel(String) must use the same * getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially * once you've called this method with a non-null getter or setter name, * you must not call #getBufferedModel(String). And vice versa, * once you've called the latter method you must not call this method * with a non-null getter or setter name. * * @param propertyName the name of the property to adapt * @param getterName the name of the method that reads the value * @param setterName the name of the method that sets the value * @return a buffered adapter to the property with the given name * on this model's bean channel using this model's trigger channel * * @throws NullPointerException if the property name is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException if this method has been called before * with the same property name and different getter or setter names * * @see BufferedValueModel * @see ValueModel * @see Trigger * @see BeanAdapter * @see #getModel(String) * @see #getBufferedModel(String) */ public BufferedValueModel getBufferedModel(String propertyName, String getterName, String setterName) { WrappedBuffer wrappedBuffer = wrappedBuffers.get(propertyName); if (wrappedBuffer == null) { wrappedBuffer = new WrappedBuffer( buffer(getModel(propertyName, getterName, setterName)), getterName, setterName); wrappedBuffers.put(propertyName, wrappedBuffer); } else { checkArgument(Objects.equals(getterName, wrappedBuffer.getterName) && Objects.equals(setterName, wrappedBuffer.setterName), "You must not invoke this method twice " + "with different getter and/or setter names."); } return wrappedBuffer.buffer; } /** * Looks up or creates a buffered component adapter to the read-write * property with the given name on this PresentationModel's bean channel. * Creates a ComponentValueModel that wraps a BufferedValueModel that * in turn wraps a ValueModel that adapts the bean property with the * specified name. The buffered model uses this PresentationModel's * trigger channel to listen for commit and flush events. * The ComponentValueModel allows to set component state in this * presentation model.

* * The created ComponentValueModel is stored in a Map. Hence * subsequent calls to this method with the same property name * return the same ComponentValueModel.

* * To prevent potential runtime errors this method eagerly looks up * the associated PropertyDescriptor if the target bean is not null.

* * For each property name all calls to this method * and to #getBufferedModel(String, String, String) must use * the same getter and setter names. Attempts to violate this constraint * will be rejected with an IllegalArgumentException. Especially once * you've called this method you must not call * #getBufferedModel(String, String, String) with a non-null * getter or setter name. And vice versa, once you've called the latter * method with a non-null getter or setter name, you must not call * this method. * * @param propertyName the name of the read-write property to adapt * @return a ComponentValueModel that wraps a buffered adapter * to the property with the given name * on this model's bean channel using this model's trigger channel * * @throws NullPointerException if the property name is null * @throws PropertyNotFoundException if the property could not be found * @throws IllegalArgumentException * if #getBufferedModel(String, String, String) has been * called before with the same property name and a non-null getter * or setter name * * @see ComponentValueModel * @see BufferedValueModel * @see ValueModel * @see Trigger * @see BeanAdapter * @see #getModel(String) * @see #getBufferedModel(String) * @see #getComponentModel(String) * @see Bindings#addComponentPropertyHandler(JComponent, ValueModel) * * @since 1.1 */ public ComponentValueModel getBufferedComponentModel(String propertyName) { ComponentValueModel bufferedComponentModel = bufferedComponentModels.get(propertyName); if (bufferedComponentModel == null) { AbstractValueModel model = getBufferedModel(propertyName); bufferedComponentModel = new ComponentValueModel(model); bufferedComponentModels.put(propertyName, bufferedComponentModel); } return bufferedComponentModel; } /** * Wraps the given ValueModel with a BufferedValueModel that * uses this model's trigger channel to trigger commit and flush events. * * @param valueModel the ValueModel to be buffered * @return a BufferedValueModel triggered by the model's trigger channel * * @see BufferedValueModel * @see ValueModel * @see Trigger * @see #getBufferedModel(String) */ private BufferedValueModel buffer(ValueModel valueModel) { BufferedValueModel bufferedModel = new BufferedValueModel( valueModel, getTriggerChannel()); bufferedModel.addPropertyChangeListener(BufferedValueModel.PROPERTYNAME_BUFFERING, bufferingUpdateHandler); return bufferedModel; } // Accessing the Trigger Channel ****************************************** /** * Returns a ValueModel that can be shared and used to trigger commit * and flush events in BufferedValueModels. The trigger channel's value * changes to true in #triggerCommit and it changes to false * in #triggerFlush.

* * This trigger channel is used to commit and flush values * in the BufferedValueModels returned by #getBufferedModel. * * @return this model's trigger channel * * @see BufferedValueModel * @see ValueModel * @see #setTriggerChannel(ValueModel) */ public ValueModel getTriggerChannel() { return triggerChannel; } /** * Sets the given ValueModel as this model's new trigger channel. * Sets the new trigger channel in all existing BufferedValueModels * that have been created using #getBufferedModel. * Subsequent invocations of #triggerCommit and * #triggerFlush will trigger commit and flush events * using the new trigger channel. * * @param newTriggerChannel the ValueModel to be set as * this model's new trigger channel * @throws NullPointerException if the new trigger channel is {@code null} * * @see BufferedValueModel * @see ValueModel * @see #getTriggerChannel() */ public void setTriggerChannel(ValueModel newTriggerChannel) { checkNotNull(newTriggerChannel, "The trigger channel must not be null."); ValueModel oldTriggerChannel = getTriggerChannel(); triggerChannel = newTriggerChannel; for (WrappedBuffer wrappedBuffer : wrappedBuffers.values()) { wrappedBuffer.buffer.setTriggerChannel(triggerChannel); } firePropertyChange( PROPERTYNAME_TRIGGERCHANNEL, oldTriggerChannel, newTriggerChannel); } /** * Sets the trigger channel to true which in turn triggers commit * events in all BufferedValueModels that share this trigger. * * @see #triggerFlush() */ public void triggerCommit() { if (Boolean.TRUE.equals(getTriggerChannel().getValue())) { getTriggerChannel().setValue(null); } getTriggerChannel().setValue(Boolean.TRUE); } /** * Sets the trigger channel to false which in turn triggers flush * events in all BufferedValueModels that share this trigger. * * @see #triggerCommit() */ public void triggerFlush() { if (Boolean.FALSE.equals(getTriggerChannel().getValue())) { getTriggerChannel().setValue(null); } getTriggerChannel().setValue(Boolean.FALSE); } // Managing the Buffering State ******************************************* /** * Answers whether any of the buffered models is buffering. * Useful to enable and disable UI actions and operations * that depend on the buffering state. * * @return true if any of the buffered models is buffering, * false, if all buffered models write-through */ public boolean isBuffering() { return buffering; } /** * Sets the buffering state to the specified value. * * @param newValue the new buffering state */ private void setBuffering(boolean newValue) { boolean oldValue = isBuffering(); buffering = newValue; firePropertyChange(PROPERTYNAME_BUFFERING, oldValue, newValue); } private void updateBufferingState(boolean latestBufferingStateChange) { if (buffering == latestBufferingStateChange) { return; } boolean nowBuffering = false; for (WrappedBuffer wrappedBuffer : wrappedBuffers.values()) { BufferedValueModel model = wrappedBuffer.buffer; nowBuffering = nowBuffering || model.isBuffering(); if (!buffering && nowBuffering) { setBuffering(true); return; } } setBuffering(nowBuffering); } // Changed State ********************************************************* /** * Answers whether one of the registered ValueModels has changed * since the changed state has been reset last time.

* * Note: Unlike #resetChanged this method * is not intended to be overridden by subclasses. * If you want to track changes of other ValueModels, bean properties, or * of submodels, register them by means of #observeChanged. * Overriding #isChanged to include the changed state * of submodels would return the correct changed value, but it would bypass * the change notification from submodels to this model. * Therefore submodels must be observed, which can be achieve using * #observeChanged.

* * To reset the changed state invoke #resetChanged. * In case you track the changed state of submodels override * #resetChanged to reset the changed state in these * submodels too. * * @return true if an observed property has changed since the last reset * * @see #observeChanged(ValueModel) * @see #observeChanged(Object, String) * @see #resetChanged() */ public boolean isChanged() { return changed; } /** * Resets this model's changed state to {@code false}. * Therefore it resets the changed states of the change tracker * and the underlying bean adapter.

* * Subclasses may override this method to reset the changed state * of submodels. The overriding method must invoke this super behavior. * For example if you have a MainModel that is composed of * two submodels Submodel1 and Submodel2, you may write: *

     * public void resetChanged() {
     *     super.resetChanged();
     *     getSubmodel1().resetChanged();
     *     getSubmodel2().resetChanged();
     * }
     * 
* * @see #isChanged() * @see #observeChanged(ValueModel) * @see #observeChanged(Object, String) */ public void resetChanged() { setChanged(false); beanAdapter.resetChanged(); } protected void setChanged(boolean newValue) { boolean oldValue = isChanged(); changed = newValue; firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue); } // Observing Changes in ValueModel and Bean Properties ******************* /** * Observes the specified readable bound bean property in the given bean. * * @param bean the bean to be observed * @param propertyName the name of the readable bound bean property * @throws NullPointerException if the bean or propertyName is null * @throws PropertyNotBindableException if this class can't add * the PropertyChangeListener from the bean * * @see #retractInterestFor(Object, String) * @see #observeChanged(ValueModel) */ public void observeChanged(Object bean, String propertyName) { checkNotNull(bean, "The bean must not be null."); checkNotNull(propertyName, "The property name must not be null."); BeanUtils.addPropertyChangeListener(bean, propertyName, changedUpdateHandler); } /** * Observes value changes in the given ValueModel. * * @param valueModel the ValueModel to observe * @throws NullPointerException if the valueModel is null * * @see #retractInterestFor(ValueModel) * @see #observeChanged(Object, String) */ public void observeChanged(ValueModel valueModel) { checkNotNull(valueModel, "The ValueModel must not be null."); valueModel.addValueChangeListener(changedUpdateHandler); } /** * Retracts interest for the specified readable bound bean property * in the given bean. * * @param bean the bean to be observed * @param propertyName the name of the readable bound bean property * @throws NullPointerException if the bean or propertyName is null * @throws PropertyNotBindableException if this class can't remove * the PropertyChangeListener from the bean * * @see #observeChanged(Object, String) * @see #retractInterestFor(ValueModel) */ public void retractInterestFor(Object bean, String propertyName) { checkNotNull(bean, "The bean must not be null."); checkNotNull(propertyName, "The property name must not be null."); BeanUtils.removePropertyChangeListener(bean, propertyName, changedUpdateHandler); } /** * Retracts interest for value changes in the given ValueModel. * * @param valueModel the ValueModel to observe * @throws NullPointerException if the valueModel is null * * @see #observeChanged(ValueModel) * @see #retractInterestFor(Object, String) */ public void retractInterestFor(ValueModel valueModel) { checkNotNull(valueModel, "The ValueModel must not be null."); valueModel.removeValueChangeListener(changedUpdateHandler); } // Managing Bean Property Change Listeners ******************************* /** * Adds a PropertyChangeListener to the list of bean listeners. The * listener is registered for all bound properties of the target bean.

* * The listener will be notified if and only if this BeanAdapter's current * bean changes a property. It'll not be notified if the bean changes.

* * If listener is {@code null}, no exception is thrown and * no action is performed. * * @param listener the PropertyChangeListener to be added * * @see #removeBeanPropertyChangeListener(PropertyChangeListener) * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners() */ public synchronized void addBeanPropertyChangeListener( PropertyChangeListener listener) { beanAdapter.addBeanPropertyChangeListener(listener); } /** * Removes a PropertyChangeListener from the list of bean listeners. * This method should be used to remove PropertyChangeListeners that * were registered for all bound properties of the target bean.

* * If listener is {@code null}, no exception is thrown and * no action is performed. * * @param listener the PropertyChangeListener to be removed * * @see #addBeanPropertyChangeListener(PropertyChangeListener) * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners() */ public synchronized void removeBeanPropertyChangeListener( PropertyChangeListener listener) { beanAdapter.removeBeanPropertyChangeListener(listener); } /** * Adds a PropertyChangeListener to the list of bean listeners for a * specific property. The specified property may be user-defined.

* * The listener will be notified if and only if this BeanAdapter's * current bean changes the specified property. It'll not be notified * if the bean changes. If you want to observe property changes and * bean changes, you may observe the ValueModel that adapts this property * - as returned by #getModel(String).

* * Note that if the bean is inheriting a bound property, then no event * will be fired in response to a change in the inherited property.

* * If listener is {@code null}, no exception is thrown and * no action is performed. * * @param propertyName one of the property names listed above * @param listener the PropertyChangeListener to be added * * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners(String) */ public synchronized void addBeanPropertyChangeListener( String propertyName, PropertyChangeListener listener) { beanAdapter.addBeanPropertyChangeListener(propertyName, listener); } /** * Removes a PropertyChangeListener from the listener list for a specific * property. This method should be used to remove PropertyChangeListeners * that were registered for a specific bound property.

* * If listener is {@code null}, no exception is thrown and * no action is performed. * * @param propertyName a valid property name * @param listener the PropertyChangeListener to be removed * * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #removeBeanPropertyChangeListener(PropertyChangeListener) * @see #getBeanPropertyChangeListeners(String) */ public synchronized void removeBeanPropertyChangeListener( String propertyName, PropertyChangeListener listener) { beanAdapter.removeBeanPropertyChangeListener(propertyName, listener); } // Requesting Listener Sets *********************************************** /** * Returns an array of all the property change listeners * registered on this component. * * @return all of this component's PropertyChangeListeners * or an empty array if no property change * listeners are currently registered * * @see #addBeanPropertyChangeListener(PropertyChangeListener) * @see #removeBeanPropertyChangeListener(PropertyChangeListener) * @see #getBeanPropertyChangeListeners(String) * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners() */ public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners() { return beanAdapter.getBeanPropertyChangeListeners(); } /** * Returns an array of all the listeners which have been associated * with the named property. * * @param propertyName the name of the property to lookup listeners * @return all of the PropertyChangeListeners associated with * the named property or an empty array if no listeners have * been added * * @see #addBeanPropertyChangeListener(String, PropertyChangeListener) * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener) * @see #getBeanPropertyChangeListeners() */ public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners(String propertyName) { return beanAdapter.getBeanPropertyChangeListeners(propertyName); } // Misc ******************************************************************* /** * Removes the PropertyChangeHandler from the observed bean, * if the bean is not {@code null}. * Also removes all listeners from the bean that have been registered * with #addBeanPropertyChangeListener before.

* * PresentationModels have a PropertyChangeListener registered with * the target bean. Hence, a bean has a reference to all PresentationModels * that hold it as bean. To avoid memory leaks it is recommended to remove * this listener, if the bean lives much longer than the PresentationModel, * enabling the garbage collector to remove the PresentationModel. * To do so, you can call setBean(null) or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with WeakReference.

* * Setting the bean to null has side-effects, for example the model * fires a change event for the bound property bean and * other properties. * And the value of ValueModel's vent by this model may change. * However, typically this is fine and setting the bean to null * is the first choice for removing the reference from the bean to * the PresentationModel. * Another way to clear the reference from the target bean is to call * this #release method; it has no side-effects.

* * Since version 2.0.4 it is safe to call this method multiple times, * however, the PresentationModel must not be used anymore once #release * has been called. * * @see #setBean(Object) * @see java.lang.ref.WeakReference * * @since 1.2 */ public void release() { beanAdapter.release(); } // Helper Class *********************************************************** /** * Holds a BufferedValueModel together with the names of the getter * and setter. Used to look up models in #getBufferedModel. * Also ensures that there are no two buffered models with different * getter/setter pairs. * * @see PresentationModel#getBufferedModel(String) * @see PresentationModel#getBufferedModel(String, String, String) */ private static final class WrappedBuffer { final BufferedValueModel buffer; final String getterName; final String setterName; WrappedBuffer( BufferedValueModel buffer, String getterName, String setterName) { this.buffer = buffer; this.getterName = getterName; this.setterName = setterName; } } // Event Handling and Forwarding Changes ********************************** /** * Listens to changes of the bean, invoked the before and after methods, * and forwards the bean change events. */ private final class BeanChangeHandler implements PropertyChangeListener { /** * The target bean will change, changes, or has changed. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { B oldBean = (B) evt.getOldValue(); B newBean = (B) evt.getNewValue(); String propertyName = evt.getPropertyName(); if (BeanAdapter.PROPERTYNAME_BEFORE_BEAN.equals(propertyName)) { beforeBeanChange(oldBean, newBean); } else if (BeanAdapter.PROPERTYNAME_BEAN.equals(propertyName)) { firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean, true); } else if (BeanAdapter.PROPERTYNAME_AFTER_BEAN.equals(propertyName)) { afterBeanChange(oldBean, newBean); } } } /** * Updates the buffering state if a model buffering state changed. */ private final class BufferingStateHandler implements PropertyChangeListener { /** * A registered BufferedValueModel has reported a change in its * buffering state. Update this model's buffering state. * * @param evt describes the property change */ public void propertyChange(PropertyChangeEvent evt) { updateBufferingState(((Boolean) evt.getNewValue()).booleanValue()); } } /** * Listens to model changes and updates the changed state. */ private final class UpdateHandler implements PropertyChangeListener { /** * A registered ValueModel has changed. * Updates the changed state. If the property that changed is * 'changed' we assume that this is another changed state and * forward only changes to true. For all other property names, * we just update our changed state to true. * * @param evt the event that describes the property change */ public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); if (!PROPERTYNAME_CHANGED.equals(propertyName) || ((Boolean) evt.getNewValue()).booleanValue()) { setChanged(true); } } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/list/0000755000175000017500000000000011374522114023350 5ustar twernertwernerjgoodies-binding-2.1.0/src/core/com/jgoodies/binding/list/IndirectListModel.java0000644000175000017500000010550011374522114027572 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.list; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.swing.ListModel; import javax.swing.event.EventListenerList; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.beans.Model; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; /** * A ListModel implementation that holds a List or ListModel in a ValueModel. * If you hold a List, this class can only report that the List has been * replaced; this is done by firing a PropertyChangeEvent for the list * property. Also, a ListDataEvent is fired that reports * a complete change. In contrast, if you use a ListModel it will report * the same PropertyChangeEvent. But fine grained changes in the ListModel * will be fired by this class to notify observes about changes in the content, * added and removed elements.

* * If the list content doesn't change at all, or if it always changes * completely, you can work well with both List content and ListModel content. * But if the list structure or content changes, the ListModel reports more * fine grained events to registered ListDataListeners, which in turn allows * list views to chooser better user interface gestures: for example, a table * with scroll pane may retain the current selection and scroll offset.

* * If you want to combine List operations and the ListModel change reports, * you may consider using an implementation that combines these two interfaces, * for example {@link com.jgoodies.common.collect.ArrayListModel} * or {@link com.jgoodies.common.collect.LinkedListModel}.

* * Important Note: If you change the ListModel instance, * either by calling #setListModel(ListModel) or by setting * a new value to the underlying list holder, you must ensure that * the list holder throws a PropertyChangeEvent whenever the instance changes. * This event is used to remove a ListDataListener from the old ListModel * instance and is later used to add it to the new ListModel instance. * It is easy to violate this constraint, just because Java's standard * PropertyChangeSupport helper class that is used by many beans, checks * a changed property value via #equals, not ==. * For example, if you change the IndirectListModel's list model from an empty * list L1 to another empty list instance L2, * the PropertyChangeSupport won't generate a PropertyChangeEvent, * and so, the IndirectListModel won't know about the change, which * may lead to unexpected behavior.

* * This binding library provides some help for firing PropertyChangeEvents * if the old ListModel and new ListModel are equal but not the same. * Class {@link com.jgoodies.binding.beans.ExtendedPropertyChangeSupport} * allows to permanently or individually check the identity (using * ==) instead of checking the equity (using #equals). * Class {@link com.jgoodies.binding.beans.Model} uses this extended * property change support. And class {@link ValueHolder} uses it too * and can be configured to always test the identity.

* * This class provides public convenience methods for firing ListDataEvents, * see the methods #fireContentsChanged, * #fireIntervalAdded, and #fireIntervalRemoved. * These are automatically invoked if the list holder holds a ListModel * that fires these events. If on the other hand the underlying List or * ListModel does not fire a required ListDataEvent, you can use these * methods to notify presentations about a change. It is recommended * to avoid sending duplicate ListDataEvents; hence check if the underlying * ListModel fires the necessary events or not.

* * Constraints: The list holder holds instances of {@link List} * or {@link ListModel}. If the ListModel changes, the underlying ValueModel * must fire a PropertyChangeEvent. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ * * @see List * @see ListModel * @see SelectionInList * @see ValueModel * @see com.jgoodies.binding.adapter.ComboBoxAdapter * @see com.jgoodies.binding.adapter.AbstractTableAdapter * @see com.jgoodies.binding.beans.ExtendedPropertyChangeSupport * @see com.jgoodies.binding.beans.Model * @see com.jgoodies.binding.value.ValueHolder * * @param the type of the list elements * * @since 2.0 */ public class IndirectListModel extends Model implements ListModel { // Constant Names for Bound Properties ************************************ /** * The name of the bound write-only list property. */ public static final String PROPERTYNAME_LIST = "list"; /** * The name of the bound read-write listHolder property. */ public static final String PROPERTYNAME_LIST_HOLDER = "listHolder"; // ************************************************************************ /** * An empty ListModel that is used if the list holder's * content is null. * * @see #getListModel() */ private static final ListModel EMPTY_LIST_MODEL = new EmptyListModel(); // Instance Fields ******************************************************** /** * Holds a List or ListModel that in turn * holds the elements. */ private ValueModel listHolder; /** * Holds a copy of the listHolder's value. Used as the old list * when the listHolder's value changes. Required because a ValueModel * may use {@code null} as old value, but the IndirectListModel * must know about the old and the new list. */ private Object list; /** * The size of the current list. Used during changes from an old * to a new list to check for shorter or longer lists, which in turn * leads to different ListDataEvents. * Required only if the old and new list are the same instance. */ private int listSize; /** * Handles changes of the list. */ private final PropertyChangeListener listChangeHandler; /** * Handles structural and content changes of the list model. */ private final ListDataListener listDataChangeHandler; /** * Refers to the list of list data listeners that is used * to notify registered listeners if the ListModel changes. */ private final EventListenerList listenerList = new EventListenerList(); // Instance creation **************************************************** /** * Constructs an IndirectListModel with an empty initial * {@code ArrayListModel}. */ public IndirectListModel() { this((ListModel) new com.jgoodies.common.collect.ArrayListModel()); } /** * Constructs an IndirectListModel on the given item array. * The specified array will be converted to a List.

* * Changes to the list "write through" to the array, and changes * to the array contents will be reflected in the list. * * @param listItems the array of initial items * * @throws NullPointerException if listItems is {@code null} */ public IndirectListModel(E[] listItems) { this(Arrays.asList(listItems)); } /** * Constructs an IndirectListModel on the given list.

* * Note: Favor ListModel over * List when working with an IndirectListModel. * Why? The IndirectListModel can work with both types. What's the * difference? ListModel provides all list access features * required by the IndirectListModel's. In addition it reports more * fine grained change events, instances of ListDataEvents. * In contrast developer often create Lists and operate on them * and the ListModel may be inconvenient for these operations.

* * A convenient solution for this situation is to use the * ArrayListModel and LinkedListModel classes. * These implement both List and ListModel, offer the standard List * operations and report the fine grained ListDataEvents. * * @param list the initial list */ public IndirectListModel(List list) { this(new ValueHolder(list, true)); } /** * Constructs an IndirectListModel on the given list model * using a default list holder. * * @param listModel the initial list model */ public IndirectListModel(ListModel listModel) { this(new ValueHolder(listModel, true)); } /** * Constructs an IndirectListModel on the given list holder.

* * Constraints: * 1) The listHolder must hold instances of List or ListModel and * 2) must report a value change whenever the value's identity changes. * Note that many bean properties don't fire a PropertyChangeEvent * if the old and new value are equal - and so would break this constraint. * If you provide a ValueHolder, enable its identityCheck feature * during construction. If you provide an adapted bean property from * a bean that extends the JGoodies Model class, * you can enable the identity check feature in the methods * #firePropertyChange by setting the trailing boolean * parameter to {@code true}. * * @param listHolder holds the list or list model * * @throws NullPointerException * if listHolder is {@code null} */ public IndirectListModel(ValueModel listHolder) { checkNotNull(listHolder, "The list holder must not be null."); checkListHolderIdentityCheck(listHolder); listChangeHandler = new ListChangeHandler(); listDataChangeHandler = createListDataChangeHandler(); this.listHolder = listHolder; this.listHolder.addValueChangeListener(listChangeHandler); // If the ValueModel holds a ListModel observe list data changes too. list = listHolder.getValue(); listSize = getSize(list); if (list != null) { if (list instanceof ListModel) { ((ListModel) list).addListDataListener(listDataChangeHandler); } else if (!(list instanceof List)) { throw new ClassCastException("The listHolder's value must be a List or ListModel."); } } } // Accessing the List/ListModel ******************************************* /** * Returns the list holder's List or an empty List, if it * holds {@code null}. Throws an exception if the list holder holds * any other type, including ListModels. * * @return the List content or an empty List if the content is {@code null} * * @throws ClassCastException if the list holder is neither * {@code null} nor a List * * @see #setList(List) * @see #getListModel() * @see #setListModel(ListModel) * * @since 2.0 */ public final List getList() { Object aList = getListHolder().getValue(); if (aList == null) { return Collections.emptyList(); } if (aList instanceof List) { return (List) aList; } throw new ClassCastException( "#getList assumes that the list holder holds a List"); } /** * Sets the given list as value of the list holder.

* * Note: Favor ListModel over * List when working with an IndirectListModel. * Why? The IndirectListModel can work with both types. What's the * difference? ListModel provides all list access features * required by the IndirectListModel's. In addition it reports more * fine grained change events, instances of ListDataEvents. * In contrast developer often create Lists and operate on them * and the ListModel may be inconvenient for these operations.

* * A convenient solution for this situation is to use the * ArrayListModel and LinkedListModel classes. * These implement both List and ListModel, offer the standard List * operations and report the fine grained ListDataEvents. * * @param newList the list to be set as new list content * * @see #getList() * @see #getListModel() * @see #setListModel(ListModel) */ public final void setList(List newList) { getListHolder().setValue(newList); } /** * Returns the list holder's ListModel or an empty ListModel, if it * holds {@code null}. Throws an exception if the list holder holds * any other type, including Lists. * * @return the ListModel content or an empty ListModel * if the content is {@code null} * * @throws ClassCastException if the list holder is neither * {@code null} nor a ListModel * * @see #setListModel(ListModel) * @see #setList(List) */ public final ListModel getListModel() { Object aListModel = getListHolder().getValue(); if (aListModel == null) { return EMPTY_LIST_MODEL; } if (aListModel instanceof ListModel) { return (ListModel) aListModel; } throw new ClassCastException( "#getListModel assumes that the list holder holds a ListModel"); } /** * Sets the given list model as value of the list holder. * * @param newListModel the list model to be set as new list content * * @see #getListModel() * @see #setList(List) */ public final void setListModel(ListModel newListModel) { getListHolder().setValue(newListModel); } // Accessing the List/ListModel Holder ************************************ /** * Returns the model that holds the List/ListModel. * * @return the model that holds the List/ListModel */ public final ValueModel getListHolder() { return listHolder; } /** * Sets a new list holder. Does nothing if old and new holder are equal. * Removes the list change handler from the old holder and adds * it to the new one. In case the list holder contents is a ListModel, * the list data change handler is updated too by invoking * #updateListDataRegistration in the same way as done in the * list change handler.

* * TODO: Check and verify whether the list data registration update * can be performed in one step after the listHolder has been * changed - instead of remove the list data change handler, then * changing the listHolder, and finally adding the list data change handler. * * @param newListHolder the list holder to be set * * @throws NullPointerException if the new list holder is {@code null} * @throws IllegalArgumentException if the listHolder is a ValueHolder * that doesn't check the identity when changing its value */ public final void setListHolder(ValueModel newListHolder) { checkNotNull(newListHolder, "The new list holder must not be null."); checkListHolderIdentityCheck(newListHolder); ValueModel oldListHolder = getListHolder(); if (oldListHolder == newListHolder) { return; } Object oldList = list; int oldSize = listSize; Object newList = newListHolder.getValue(); oldListHolder.removeValueChangeListener(listChangeHandler); listHolder = newListHolder; newListHolder.addValueChangeListener(listChangeHandler); updateList(oldList, oldSize, newList); firePropertyChange(PROPERTYNAME_LIST_HOLDER, oldListHolder, newListHolder); } // ListModel Implementation *********************************************** /** * Checks and answers if the list is empty or {@code null}. * * @return true if the list is empty or {@code null}, false otherwise */ public final boolean isEmpty() { return getSize() == 0; } /** * Returns the length of the list, 0 if the list model * is {@code null}. * * @return the size of the list, 0 if the list model is * {@code null} */ public final int getSize() { return getSize(getListHolder().getValue()); } /** * Returns the value at the specified index, {@code null} * if the list model is {@code null}. * * @param index the requested index * @return the value at index, {@code null} * if the list model is {@code null} * * @throws NullPointerException if the list holder's content is null */ public final E getElementAt(int index) { return getElementAt(getListHolder().getValue(), index); } /** * Adds a listener to the list that's notified each time a change * to the data model occurs. * * @param l the ListDataListener to be added */ public final void addListDataListener(ListDataListener l) { listenerList.add(ListDataListener.class, l); } /** * Removes a listener from the list that's notified each time a * change to the data model occurs. * * @param l the ListDataListener to be removed */ public final void removeListDataListener(ListDataListener l) { listenerList.remove(ListDataListener.class, l); } /** * Returns an array of all the list data listeners * registered on this IndirectListModel. * * @return all of this model's ListDataListeners, * or an empty array if no list data listeners * are currently registered * * @see #addListDataListener(ListDataListener) * @see #removeListDataListener(ListDataListener) */ public final ListDataListener[] getListDataListeners() { return listenerList.getListeners(ListDataListener.class); } // ListModel Helper Code ************************************************** /** * Notifies all registered ListDataListeners that the contents * of one or more list elements has changed. * The changed elements are specified by the closed interval index0, index1 * -- the end points are included. Note that index0 need not be less than * or equal to index1.

* * If the list holder holds a ListModel, this IndirectListModel listens * to ListDataEvents fired by that ListModel, and forwards these events * by invoking the associated #fireXXX method, which in turn * notifies all registered ListDataListeners. Therefore if you fire * ListDataEvents in an underlying ListModel, you don't need this method * and should not use it to avoid sending duplicate ListDataEvents. * * @param index0 one end of the new interval * @param index1 the other end of the new interval * * @see ListModel * @see ListDataListener * @see ListDataEvent * * @since 1.0.2 */ public final void fireContentsChanged(int index0, int index1) { Object[] listeners = listenerList.getListenerList(); ListDataEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListDataListener.class) { if (e == null) { e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index0, index1); } ((ListDataListener) listeners[i + 1]).contentsChanged(e); } } } /** * Notifies all registered ListDataListeners that one or more elements * have been added to this IndirectListModel's List/ListModel. * The new elements are specified by a closed interval index0, index1 * -- the end points are included. Note that index0 need not be less than * or equal to index1.

* * If the list holder holds a ListModel, this IndirectListModel listens * to ListDataEvents fired by that ListModel, and forwards these events * by invoking the associated #fireXXX method, which in turn * notifies all registered ListDataListeners. Therefore if you fire * ListDataEvents in an underlying ListModel, you don't need this method * and should not use it to avoid sending duplicate ListDataEvents. * * @param index0 one end of the new interval * @param index1 the other end of the new interval * * @see ListModel * @see ListDataListener * @see ListDataEvent * * @since 1.0.2 */ public final void fireIntervalAdded(int index0, int index1) { Object[] listeners = listenerList.getListenerList(); ListDataEvent e = null; listSize = getSize(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListDataListener.class) { if (e == null) { e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index0, index1); } ((ListDataListener) listeners[i + 1]).intervalAdded(e); } } } /** * Notifies all registered ListDataListeners that one or more elements * have been removed from this IndirectListModel's List/ListModel. * index0 and index1 are the end points * of the interval that's been removed. Note that index0 * need not be less than or equal to index1.

* * If the list holder holds a ListModel, this IndirectListModel listens * to ListDataEvents fired by that ListModel, and forwards these events * by invoking the associated #fireXXX method, which in turn * notifies all registered ListDataListeners. Therefore if you fire * ListDataEvents in an underlying ListModel, you don't need this method * and should not use it to avoid sending duplicate ListDataEvents. * * @param index0 one end of the removed interval, * including index0 * @param index1 the other end of the removed interval, * including index1 * * @see ListModel * @see ListDataListener * @see ListDataEvent * * @since 1.0.2 */ public final void fireIntervalRemoved(int index0, int index1) { Object[] listeners = listenerList.getListenerList(); ListDataEvent e = null; listSize = getSize(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListDataListener.class) { if (e == null) { e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index0, index1); } ((ListDataListener) listeners[i + 1]).intervalRemoved(e); } } } // Misc ****************************************************************** /** * Removes the internal listeners from the list holder. If the current list * is a ListModel, the internal ListDataListener is removed from it. * This IndirectListModel must not be used after calling * #release.

* * To avoid memory leaks it is recommended to invoke this method, * if the list holder, selection holder, or selection index holder * live much longer than this IndirectListModel. * Instead of releasing the IndirectListModel, you typically make the * list holder obsolete by releasing the PresentationModel or BeanAdapter * that has created them before.

* * As an alternative you may use ValueModels that in turn use * event listener lists implemented using WeakReference.

* * Basically this release method performs the reverse operation * performed during the IndirectListModel construction. * * @see PresentationModel#release() * @see BeanAdapter#release() * @see java.lang.ref.WeakReference * * @since 1.2 */ public void release() { listHolder.removeValueChangeListener(listChangeHandler); if (list != null && list instanceof ListModel) { ((ListModel) list).removeListDataListener(listDataChangeHandler); } listHolder = null; list = null; } // Default Behavior ******************************************************* /** * Creates and returns the ListDataListener used to observe * changes in the underlying ListModel. It is re-registered * in #updateListModel. * * @return the ListDataListener that handles changes * in the underlying ListModel */ protected ListDataListener createListDataChangeHandler() { return new ListDataChangeHandler(); } /** * Removes the list data change handler from the old list in case * it is a ListModel and adds it to new one in case * it is a ListModel. * It then fires a property change for the list and a contents change event * for the list content. * * @param oldList the old list content * @param oldSize the size of the old List content * @param newList the new list content * * @see javax.swing.JTable#tableChanged(javax.swing.event.TableModelEvent) */ protected void updateList(Object oldList, int oldSize, Object newList) { if (oldList != null && oldList instanceof ListModel) { ((ListModel) oldList).removeListDataListener(listDataChangeHandler); } if (newList != null && newList instanceof ListModel) { ((ListModel) newList).addListDataListener(listDataChangeHandler); } int newSize = getSize(newList); list = newList; listSize = getSize(newList); firePropertyChange(PROPERTYNAME_LIST, oldList, newList); fireListChanged(oldSize - 1, newSize - 1); } /** * Notifies all registered ListDataListeners that this ListModel * has changed from an old list to a new list content. * If the old and new list size differ, a remove or add event for * the removed or added interval is fired. A content change * is reported for the interval common to the old and new list.

* * This method is invoked by #updateList during the transition * from an old List(Model) to a new List(Model).

* * Note: * The order of the events fired ensures that after each event * the size described by the ListDataEvents equals the ListModel size. * * @param oldLastIndex the last index of the old list * @param newLastIndex the last index of the new list */ protected final void fireListChanged(int oldLastIndex, int newLastIndex) { if (newLastIndex < oldLastIndex) { fireIntervalRemoved(newLastIndex + 1, oldLastIndex); } else if (oldLastIndex < newLastIndex) { fireIntervalAdded(oldLastIndex + 1, newLastIndex); } int lastCommonIndex = Math.min(oldLastIndex, newLastIndex); if (lastCommonIndex >= 0) { fireContentsChanged(0, lastCommonIndex); } } // Helper Code ************************************************************ /** * Returns the length of the given list, 0 if the list model * is {@code null}. * * @param aListListModelOrNull a List, ListModel or null * @return the size of the given list, 0 if the list model is * {@code null} */ protected final int getSize(Object aListListModelOrNull) { if (aListListModelOrNull == null) { return 0; } else if (aListListModelOrNull instanceof ListModel) { return ((ListModel) aListListModelOrNull).getSize(); } else { return ((List) aListListModelOrNull).size(); } } private E getElementAt(Object aList, int index) { checkNotNull(aList, "The list contents is null."); if (aList instanceof ListModel) { return (E) ((ListModel) aList).getElementAt(index); } return ((List) aList).get(index); } /** * Throws an IllegalArgumentException if the given ValueModel * is a ValueHolder that has the identityCheck feature disabled. */ private void checkListHolderIdentityCheck(ValueModel aListHolder) { if (!(aListHolder instanceof ValueHolder)) { return; } ValueHolder valueHolder = (ValueHolder) aListHolder; checkArgument(valueHolder.isIdentityCheckEnabled(), "The list holder must have the identity check enabled."); } // Helper Classes ********************************************************* /** * A ListModel that has no elements, a size of 0, and never fires an event. */ private static final class EmptyListModel implements ListModel, Serializable { /** * Returns zero to indicate an empty list. */ public int getSize() { return 0; } /** * Returns {@code null} because this model has no elements. */ public Object getElementAt(int index) { return null; } /** * Does nothing, because the empty list will never fire an event. * * @param l the ListDataListener to be ignored */ public void addListDataListener(ListDataListener l) { // Do nothing. } /** * Does nothing, because the empty list will never fire an event. * * @param l the ListDataListener to be ignored */ public void removeListDataListener(ListDataListener l) { // Do nothing. } } // Event Handlers ********************************************************* /** * Handles changes of the List or ListModel. */ private final class ListChangeHandler implements PropertyChangeListener { /** * The list has been changed. * Notifies all registered listeners about the change. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { Object oldList = list; int oldSize = listSize; Object newList = evt.getNewValue(); updateList(oldList, oldSize, newList); } } /** * Handles ListDataEvents in the list model. */ private final class ListDataChangeHandler implements ListDataListener { /** * Sent after the indices in the index0, index1 * interval have been inserted in the data model. * The new interval includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalAdded(ListDataEvent evt) { int index0 = evt.getIndex0(); int index1 = evt.getIndex1(); fireIntervalAdded(index0, index1); } /** * Sent after the indices in the index0, index1 interval * have been removed from the data model. The interval * includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalRemoved(ListDataEvent evt) { int index0 = evt.getIndex0(); int index1 = evt.getIndex1(); fireIntervalRemoved(index0, index1); } /** * Sent when the contents of the list has changed in a way * that's too complex to characterize with the previous * methods. For example, this is sent when an item has been * replaced. Index0 and index1 bracket the change. * * @param evt a ListDataEvent encapsulating the * event information */ public void contentsChanged(ListDataEvent evt) { fireContentsChanged(evt.getIndex0(), evt.getIndex1()); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/list/LinkedListModel.java0000644000175000017500000000601311374522114027236 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.list; import java.util.Collection; /** * Adds {@link javax.swing.ListModel} capabilities to its superclass * LinkedList, i. e. allows to observe changes in the content and * structure. Useful for lists that are bound to list views, for example * JList, JComboBox and JTable. * * @author Karsten Lentzsch * @version $Revision: 1.13 $ * * @param the type of the list elements * * @deprecated Replaced by {@link com.jgoodies.common.collect.LinkedListModel}. * This class will be removed from the next library version. */ @Deprecated public final class LinkedListModel extends com.jgoodies.common.collect.LinkedListModel implements ObservableList { // Instance Creation ****************************************************** /** * Constructs an empty linked list. */ public LinkedListModel() { // Just invoke the super constructor implicitly. } /** * Constructs a linked list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list. * @throws NullPointerException if the specified collection is * {@code null} */ public LinkedListModel(Collection c) { super(c); } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/list/SelectionInList.java0000644000175000017500000014761011374522114027274 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.list; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; import java.util.List; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import com.jgoodies.binding.PresentationModel; import com.jgoodies.binding.beans.BeanAdapter; import com.jgoodies.binding.value.ValueHolder; import com.jgoodies.binding.value.ValueModel; import com.jgoodies.common.base.Objects; /** * Represents a selection in a list of objects. Provides bound bean properties * for the list, the selection, the selection index, and the selection empty * state. The SelectionInList implements ValueModel with the selection as value. * Selection changes fire an event only if the old and new value are not equal. * If you need to compare the identity you can use and observe the selection * index instead of the selection or value.

* * The SelectionInList uses three ValueModels to hold the list, the selection * and selection index and provides bound bean properties for these models. * You can access, observe and replace these ValueModels. This is useful * to connect a SelectionInList with other ValueModels; for example you can * use the SelectionInList's selection holder as bean channel for a * PresentationModel. Since the SelectionInList is a ValueModel, it is often * used as bean channel. See the Binding tutorial classes for examples on how * to connect a SelectionInList with a PresentationModel.

* * This class also implements the {@link ListModel} interface that allows * API users to observe fine grained changes in the structure and contents * of the list. Hence instances of this class can be used directly as model of * a JList. If you want to use a SelectionInList with a JComboBox or JTable, * you can convert the SelectionInList to the associated component model * interfaces using the adapter classes * {@link com.jgoodies.binding.adapter.ComboBoxAdapter} * and {@link com.jgoodies.binding.adapter.AbstractTableAdapter} respectively. * These classes are part of the Binding library too.

* * The SelectionInList supports two list types as content of its list holder: * List and ListModel. The two modes differ in how * precise this class can fire events about changes to the content and structure * of the list. If you use a List, this class can only report * that the list changes completely; this is done by firing * a PropertyChangeEvent for the list property. * Also, a ListDataEvent is fired that reports a complete change. * In contrast, if you use a ListModel it will report the same * PropertyChangeEvent. But fine grained changes in the list * model will be fired by this class to notify observes about changes in * the content, added and removed elements.

* * If the list content doesn't change at all, or if it always changes * completely, you can work well with both List content and ListModel content. * But if the list structure or content changes, the ListModel reports more * fine grained events to registered ListDataListeners, which in turn allows * list views to chooser better user interface gestures: for example, a table * with scroll pane may retain the current selection and scroll offset.

* * An example for using a ListModel in a SelectionInList is the asynchronous * transport of list elements from a server to a client. Let's say you transport * the list elements in portions of 10 elements to improve the application's * responsiveness. The user can then select and work with the SelectionInList * as soon as the ListModel gets populated. If at a later time more elements * are added to the list model, the SelectionInList can retain the selection * index (and selection) and will just report a ListDataEvent about * the interval added. JList, JTable and JComboBox will then just add * the new elements at the end of the list presentation.

* * If you want to combine List operations and the ListModel change reports, * you may consider using an implementation that combines these two interfaces, * for example {@link com.jgoodies.common.collect.ArrayListModel} * or {@link com.jgoodies.common.collect.LinkedListModel}.

* * Important Note: If you change the ListModel instance, * either by calling #setListModel(ListModel) or by setting * a new value to the underlying list holder, you must ensure that * the list holder throws a PropertyChangeEvent whenever the instance changes. * This event is used to remove a ListDataListener from the old ListModel * instance and is later used to add it to the new ListModel instance. * It is easy to violate this constraint, just because Java's standard * PropertyChangeSupport helper class that is used by many beans, checks * a changed property value via #equals, not ==. * For example, if you change the SelectionInList's list model from an empty * list L1 to another empty list instance L2, * the PropertyChangeSupport won't generate a PropertyChangeEvent, * and so, the SelectionInList won't know about the change, which * may lead to unexpected behavior.

* * This binding library provides some help for firing PropertyChangeEvents * if the old ListModel and new ListModel are equal but not the same. * Class {@link com.jgoodies.binding.beans.ExtendedPropertyChangeSupport} * allows to permanently or individually check the identity (using * ==) instead of checking the equity (using #equals). * Class {@link com.jgoodies.binding.beans.Model} uses this extended * property change support. And class {@link ValueHolder} uses it too * and can be configured to always test the identity.

* * Since version 1.0.2 this class provides public convenience methods * for firing ListDataEvents, see the methods #fireContentsChanged, * #fireIntervalAdded, and #fireIntervalRemoved. * These are automatically invoked if the list holder holds a ListModel * that fires these events. If on the other hand the underlying List or * ListModel does not fire a required ListDataEvent, you can use these * methods to notify presentations about a change. It is recommended * to avoid sending duplicate ListDataEvents; hence check if the underlying * ListModel fires the necessary events or not. Typically an underlying * ListModel will fire the add and remove events; but often it'll lack * an event if the (selected) contents has changed. A convenient way to * indicate that change is #fireSelectedContentsChanged. See * the tutorial's AlbumManagerModel for an example how to use this feature.

* * The SelectionInList is partially defined for Lists and ListModels * that contain {@code null}. Setting the selection to {@code null} * on a SelectionInList that contains {@code null} won't set the selection index * to the index of the first {@code null} element. For details see the * {@link #setSelection(Object)} JavaDocs. This is because the current * implementation interprets a {@code null} selection as unspecified, * which maps better to a cleared selection than to a concrete selection index. * Anyway, as long as you work with the selection index and selection index * holder, such a SelectionInList will work fine. This is the case if you bind * a SelectionInList to a JList or JTable. Binding such a SelectionInList * to a JComboBox won't synchronize the selection index if {@code null} * is selected.

* * Constraints: The list holder holds instances of {@link List} * or {@link ListModel}, the selection holder values of type {@code E} * and the selection index holder of type {@code Integer}. The selection * index holder must hold non-null index values; however, when firing * an index value change event, both the old and new value may be null. * If the ListModel changes, the underlying ValueModel must fire * a PropertyChangeEvent. * * @author Karsten Lentzsch * @version $Revision: 1.42 $ * * @see ValueModel * @see List * @see ListModel * @see com.jgoodies.binding.adapter.ComboBoxAdapter * @see com.jgoodies.binding.adapter.AbstractTableAdapter * @see com.jgoodies.binding.beans.ExtendedPropertyChangeSupport * @see com.jgoodies.binding.beans.Model * @see com.jgoodies.binding.value.ValueHolder * * @param the type of the list elements and the selection */ public final class SelectionInList extends IndirectListModel implements ValueModel { // Constant Names for Bound Properties ************************************ /** * The name of the bound read-write selection property. */ public static final String PROPERTYNAME_SELECTION = "selection"; /** * The name of the bound read-only selectionEmpty property. */ public static final String PROPERTYNAME_SELECTION_EMPTY = "selectionEmpty"; /** * The name of the bound read-write selection holder property. */ public static final String PROPERTYNAME_SELECTION_HOLDER = "selectionHolder"; /** * The name of the bound read-write selectionIndex property. */ public static final String PROPERTYNAME_SELECTION_INDEX = "selectionIndex"; /** * The name of the bound read-write selection index holder property. */ public static final String PROPERTYNAME_SELECTION_INDEX_HOLDER = "selectionIndexHolder"; /** * The name of the bound read-write value property. */ public static final String PROPERTYNAME_VALUE = "value"; // ************************************************************************ /** * A special index that indicates that we have no selection. */ private static final int NO_SELECTION_INDEX = -1; // Instance Fields ******************************************************** /** * Holds the selection, an instance of Object. */ private ValueModel selectionHolder; /** * Holds the selection index, an Integer. */ private ValueModel selectionIndexHolder; /** * The PropertyChangeListener used to handle * changes of the selection. */ private final PropertyChangeListener selectionChangeHandler; /** * The PropertyChangeListener used to handle * changes of the selection index. */ private final PropertyChangeListener selectionIndexChangeHandler; /** * Duplicates the value of the selectionHolder. * Used to provide better old values in PropertyChangeEvents * fired after selectionIndex changes. */ private E oldSelection; /** * Duplicates the value of the selectionIndexHolder. * Used to provide better old values in PropertyChangeEvents * fired after selectionIndex changes and selection changes. */ private int oldSelectionIndex; // Instance creation **************************************************** /** * Constructs a SelectionInList with an empty initial * ArrayListModel using defaults for the selection holder * and selection index holder. */ public SelectionInList() { this((ListModel) new com.jgoodies.common.collect.ArrayListModel()); } /** * Constructs a SelectionInList on the given item array * using defaults for the selection holder and selection index holder. * The specified array will be converted to a List.

* * Changes to the list "write through" to the array, and changes * to the array contents will be reflected in the list. * * @param listItems the array of initial items * * @throws NullPointerException if listItems is {@code null} */ public SelectionInList(E[] listItems) { this(Arrays.asList(listItems)); } /** * Constructs a SelectionInList on the given item array and * selection holder using a default selection index holder. * The specified array will be converted to a List.

* * Changes to the list "write through" to the array, and changes * to the array contents will be reflected in the list. * * @param listItems the array of initial items * @param selectionHolder holds the selection * * @throws NullPointerException if listItems or * selectionHolder is {@code null} */ public SelectionInList(E[] listItems, ValueModel selectionHolder) { this(Arrays.asList(listItems), selectionHolder); } /** * Constructs a SelectionInList on the given item array and * selection holder using a default selection index holder. * The specified array will be converted to a List.

* * Changes to the list "write through" to the array, and changes * to the array contents will be reflected in the list. * * @param listItems the array of initial items * @param selectionHolder holds the selection * @param selectionIndexHolder holds the selection index * * @throws NullPointerException if listItems, * selectionHolder, or selectionIndexHolder * is {@code null} */ public SelectionInList( E[] listItems, ValueModel selectionHolder, ValueModel selectionIndexHolder) { this(Arrays.asList(listItems), selectionHolder, selectionIndexHolder); } /** * Constructs a SelectionInList on the given list * using defaults for the selection holder and selection index holder.

* * Note: Favor ListModel over * List when working with the SelectionInList. * Why? The SelectionInList can work with both types. What's the * difference? ListModel provides all list access features * required by the SelectionInList's. In addition it reports more * fine grained change events, instances of ListDataEvents. * In contrast developer often create Lists and operate on them * and the ListModel may be inconvenient for these operations.

* * A convenient solution for this situation is to use the * ArrayListModel and LinkedListModel classes. * These implement both List and ListModel, offer the standard List * operations and report the fine grained ListDataEvents. * * @param list the initial list */ public SelectionInList(List list) { this(new ValueHolder(list, true)); } /** * Constructs a SelectionInList on the given list and * selection holder using a default selection index holder.

* * Note: Favor ListModel over * List when working with the SelectionInList. * Why? The SelectionInList can work with both types. What's the * difference? ListModel provides all list access features * required by the SelectionInList's. In addition it reports more * fine grained change events, instances of ListDataEvents. * In contrast developer often create Lists and operate on them * and the ListModel may be inconvenient for these operations.

* * A convenient solution for this situation is to use the * ArrayListModel and LinkedListModel classes. * These implement both List and ListModel, offer the standard List * operations and report the fine grained ListDataEvents. * * @param list the initial list * @param selectionHolder holds the selection * * @throws NullPointerException * if selectionHolder is {@code null} */ public SelectionInList(List list, ValueModel selectionHolder) { this(new ValueHolder(list, true), selectionHolder); } /** * Constructs a SelectionInList on the given list, * selection holder, and selection index holder.

* * Note: Favor ListModel over * List when working with the SelectionInList. * Why? The SelectionInList can work with both types. What's the * difference? ListModel provides all list access features * required by the SelectionInList's. In addition it reports more * fine grained change events, instances of ListDataEvents. * In contrast developer often create Lists and operate on them * and the ListModel may be inconvenient for these operations.

* * A convenient solution for this situation is to use the * ArrayListModel and LinkedListModel classes. * These implement both List and ListModel, offer the standard List * operations and report the fine grained ListDataEvents. * * @param list the initial list * @param selectionHolder holds the selection * @param selectionIndexHolder holds the selection index * * @throws NullPointerException if selectionHolder, * or selectionIndexHolder is {@code null} */ public SelectionInList( List list, ValueModel selectionHolder, ValueModel selectionIndexHolder) { this(new ValueHolder(list, true), selectionHolder, selectionIndexHolder); } /** * Constructs a SelectionInList on the given list model * using defaults for the selection holder and selection index holder. * * @param listModel the initial list model */ public SelectionInList(ListModel listModel) { this(new ValueHolder(listModel, true)); } /** * Constructs a SelectionInList on the given list model * and selection holder using a default selection index holder. * * @param listModel the initial list model * @param selectionHolder holds the selection * * @throws NullPointerException * if selectionHolder is {@code null} */ public SelectionInList(ListModel listModel, ValueModel selectionHolder) { this(new ValueHolder(listModel, true), selectionHolder); } /** * Constructs a SelectionInList on the given list model, * selection holder, and selection index holder. * * @param listModel the initial list model * @param selectionHolder holds the selection * @param selectionIndexHolder holds the selection index * * @throws NullPointerException if selectionHolder, * or selectionIndexHolder is {@code null} */ public SelectionInList( ListModel listModel, ValueModel selectionHolder, ValueModel selectionIndexHolder) { this(new ValueHolder(listModel, true), selectionHolder, selectionIndexHolder); } /** * Constructs a SelectionInList on the given list holder * using defaults for the selection holder and selection index holder.

* * Constraints: * 1) The listHolder must hold instances of List or ListModel and * 2) must report a value change whenever the value's identity changes. * Note that many bean properties don't fire a PropertyChangeEvent * if the old and new value are equal - and so would break this constraint. * If you provide a ValueHolder, enable its identityCheck feature * during construction. If you provide an adapted bean property from * a bean that extends the JGoodies Model class, * you can enable the identity check feature in the methods * #firePropertyChange by setting the trailing boolean * parameter to {@code true}. * * @param listHolder holds the list or list model * * @throws NullPointerException * if listHolder is {@code null} */ public SelectionInList(ValueModel listHolder) { this(listHolder, new ValueHolder(null, true)); } /** * Constructs a SelectionInList on the given list holder, * selection holder and selection index holder.

* * Constraints: * 1) The listHolder must hold instances of List or ListModel and * 2) must report a value change whenever the value's identity changes. * Note that many bean properties don't fire a PropertyChangeEvent * if the old and new value are equal - and so would break this constraint. * If you provide a ValueHolder, enable its identityCheck feature * during construction. If you provide an adapted bean property from * a bean that extends the JGoodies Model class, * you can enable the identity check feature in the methods * #firePropertyChange by setting the trailing boolean * parameter to {@code true}. * * @param listHolder holds the list or list model * @param selectionHolder holds the selection * @throws NullPointerException if listHolder * or selectionHolder is {@code null} */ public SelectionInList(ValueModel listHolder, ValueModel selectionHolder) { this( listHolder, selectionHolder, new ValueHolder(Integer.valueOf(NO_SELECTION_INDEX))); } /** * Constructs a SelectionInList on the given list holder, * selection holder and selection index holder.

* * Constraints: * 1) The listHolder must hold instances of List or ListModel and * 2) must report a value change whenever the value's identity changes. * Note that many bean properties don't fire a PropertyChangeEvent * if the old and new value are equal - and so would break this constraint. * If you provide a ValueHolder, enable its identityCheck feature * during construction. If you provide an adapted bean property from * a bean that extends the JGoodies Model class, * you can enable the identity check feature in the methods * #firePropertyChange by setting the trailing boolean * parameter to {@code true}. * * @param listHolder holds the list or list model * @param selectionHolder holds the selection * @param selectionIndexHolder holds the selection index * * @throws NullPointerException if the listModelHolder, * selectionHolder, or selectionIndexHolder * is {@code null} * @throws IllegalArgumentException if the listHolder is a ValueHolder * that doesn't check the identity when changing its value * @throws ClassCastException if the listModelHolder contents * is neither a List nor a ListModel */ public SelectionInList( ValueModel listHolder, ValueModel selectionHolder, ValueModel selectionIndexHolder) { super(listHolder); this.selectionHolder = checkNotNull(selectionHolder, "The selection holder must not be null."); this.selectionIndexHolder = checkNotNull(selectionIndexHolder, "The selection index holder must not be null."); selectionChangeHandler = new SelectionChangeHandler(); selectionIndexChangeHandler = new SelectionIndexChangeHandler(); initializeSelectionIndex(); this.selectionHolder.addValueChangeListener(selectionChangeHandler); this.selectionIndexHolder.addValueChangeListener(selectionIndexChangeHandler); } // ListModel Helper Code ************************************************** /** * Notifies all registered ListDataListeners that the contents * of the selected list item - if any - has changed. * Useful to update a presentation after editing the selection. * See the tutorial's AlbumManagerModel for an example how to use * this feature.

* * If the list holder holds a ListModel, this SelectionInList listens * to ListDataEvents fired by that ListModel, and forwards these events * by invoking the associated #fireXXX method, which in turn * notifies all registered ListDataListeners. Therefore if you fire * ListDataEvents in an underlying ListModel, you don't need this method * and should not use it to avoid sending duplicate ListDataEvents. * * @see ListModel * @see ListDataListener * @see ListDataEvent * * @since 1.0.2 */ public void fireSelectedContentsChanged() { if (hasSelection()) { int selectionIndex = getSelectionIndex(); fireContentsChanged(selectionIndex, selectionIndex); } } // Accessing the List, Selection and Index ******************************** /** * Looks up and returns the current selection using * the current selection index. Returns {@code null} if * no object is selected or if the list has no elements. * * @return the current selection, {@code null} if none is selected */ public E getSelection() { return getSafeElementAt(getSelectionIndex()); } /** * Sets the selection index to the index of the first list element * that equals {@code newSelection}. If {@code newSelection} * is {@code null}, it is interpreted as unspecified * and the selection index is set to -1, and this SelectionInList * has no selection. Does nothing if the list is empty or {@code null}. * * @param newSelection the object to be set as new selection, * or {@code null} to set the selection index to -1 */ public void setSelection(E newSelection) { if (!isEmpty()) { setSelectionIndex(indexOf(newSelection)); } } /** * Checks and answers if an element is selected. * * @return true if an element is selected, false otherwise */ public boolean hasSelection() { return getSelectionIndex() != NO_SELECTION_INDEX; } /** * Checks and answers whether the selection is empty or not. * Unlike #hasSelection, the underlying property #selectionEmpty * for this method is bound. I.e. you can observe this property * using a PropertyChangeListener to update UI state. * * @return true if nothing is selected, false if there's a selection * @see #clearSelection * @see #hasSelection */ public boolean isSelectionEmpty() { return !hasSelection(); } /** * Clears the selection of this SelectionInList - if any. */ public void clearSelection() { setSelectionIndex(NO_SELECTION_INDEX); } /** * Returns the selection index. * * @return the selection index * * @throws NullPointerException if the selection index holder * has a null Object set */ public int getSelectionIndex() { return ((Integer) getSelectionIndexHolder().getValue()).intValue(); } /** * Sets a new selection index. Does nothing if it is the same as before. * * @param newSelectionIndex the selection index to be set * @throws IndexOutOfBoundsException if the new selection index * is outside the bounds of the list */ public void setSelectionIndex(int newSelectionIndex) { int upperBound = getSize() - 1; if (newSelectionIndex < NO_SELECTION_INDEX || newSelectionIndex > upperBound) { throw new IndexOutOfBoundsException( "The selection index " + newSelectionIndex + " must be in [-1, " + upperBound + "]"); } oldSelectionIndex = getSelectionIndex(); if (oldSelectionIndex == newSelectionIndex) { return; } getSelectionIndexHolder().setValue(Integer.valueOf(newSelectionIndex)); } // Accessing the Holders for: List, Selection and Index ******************* /** * Returns the selection holder. * * @return the selection holder */ public ValueModel getSelectionHolder() { return selectionHolder; } /** * Sets a new selection holder. * Does nothing if the new is the same as before. * The selection remains unchanged and is still driven * by the selection index holder. It's just that future * index changes will update the new selection holder * and that future selection holder changes affect the * selection index. * * @param newSelectionHolder the selection holder to set * * @throws NullPointerException if the new selection holder is null */ public void setSelectionHolder(ValueModel newSelectionHolder) { checkNotNull(newSelectionHolder, "The new selection holder must not be null."); ValueModel oldSelectionHolder = getSelectionHolder(); oldSelectionHolder.removeValueChangeListener(selectionChangeHandler); selectionHolder = newSelectionHolder; oldSelection = (E) newSelectionHolder.getValue(); newSelectionHolder.addValueChangeListener(selectionChangeHandler); firePropertyChange(PROPERTYNAME_SELECTION_HOLDER, oldSelectionHolder, newSelectionHolder); } /** * Returns the selection index holder. * * @return the selection index holder */ public ValueModel getSelectionIndexHolder() { return selectionIndexHolder; } /** * Sets a new selection index holder. * Does nothing if the new is the same as before. * * @param newSelectionIndexHolder the selection index holder to set * * @throws NullPointerException if the new selection index holder is null * @throws IllegalArgumentException if the value of the new selection index * holder is null */ public void setSelectionIndexHolder(ValueModel newSelectionIndexHolder) { checkNotNull(newSelectionIndexHolder, "The new selection index holder must not be null."); checkArgument(newSelectionIndexHolder.getValue() != null, "The value of the new selection index holder must not be null."); ValueModel oldSelectionIndexHolder = getSelectionIndexHolder(); if (Objects.equals(oldSelectionIndexHolder, newSelectionIndexHolder)) { return; } oldSelectionIndexHolder.removeValueChangeListener(selectionIndexChangeHandler); selectionIndexHolder = newSelectionIndexHolder; newSelectionIndexHolder.addValueChangeListener(selectionIndexChangeHandler); oldSelectionIndex = getSelectionIndex(); oldSelection = getSafeElementAt(oldSelectionIndex); firePropertyChange(PROPERTYNAME_SELECTION_INDEX_HOLDER, oldSelectionIndexHolder, newSelectionIndexHolder); } // ValueModel Implementation ******************************************** /** * Returns the current selection, {@code null} if the selection index * does not represent a selection in the list. * * @return the selected element - if any */ public E getValue() { return getSelection(); } /** * Sets the selection index to the index of the first list element * that equals {@code newValue}. If {@code newValue} * is {@code null}, it is interpreted as unspecified * and the selection index is set to -1, and this SelectionInList * has no selection. Does nothing if the list is empty or {@code null}. * * @param newValue the object to be set as new selection, * or {@code null} to set the selection index to -1 */ public void setValue(Object newValue) { setSelection((E) newValue); } /** * Registers the given PropertyChangeListener with this model. * The listener will be notified if the value has changed.

* * The PropertyChangeEvents delivered to the listener have the name * set to "value". In other words, the listeners won't get notified * when a PropertyChangeEvent is fired that has a null object as * the name to indicate an arbitrary set of the event source's * properties have changed.

* * In the rare case, where you want to notify a PropertyChangeListener * even with PropertyChangeEvents that have no property name set, * you can register the listener with #addPropertyChangeListener, * not #addValueChangeListener. * * @param l the listener to add * * @see ValueModel */ public void addValueChangeListener(PropertyChangeListener l) { addPropertyChangeListener(PROPERTYNAME_VALUE, l); } /** * Removes the given PropertyChangeListener from the model. * * @param l the listener to remove */ public void removeValueChangeListener(PropertyChangeListener l) { removePropertyChangeListener(PROPERTYNAME_VALUE, l); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the value before the change * @param newValue the value after the change * * @see java.beans.PropertyChangeSupport */ void fireValueChange(Object oldValue, Object newValue) { firePropertyChange(PROPERTYNAME_VALUE, oldValue, newValue); } // Misc ****************************************************************** /** * Removes the internal listeners from the list holder, selection holder, * selection index holder. If the current list is a ListModel, the internal * ListDataListener is removed from the list model. This SelectionInList * must not be used after calling #release.

* * To avoid memory leaks it is recommended to invoke this method, * if the list holder, selection holder, or selection index holder * live much longer than this SelectionInList. * Instead of releasing the SelectionInList, you typically make * the list holder, selection holder, and selection index holder * obsolete by releasing the PresentationModel or BeanAdapter that has * created them before.

* * As an alternative you may use ValueModels that in turn use * event listener lists implemented using WeakReference.

* * Basically this release method performs the reverse operation * performed during the SelectionInList construction. * * @see PresentationModel#release() * @see BeanAdapter#release() * @see java.lang.ref.WeakReference * * @since 1.2 */ @Override public void release() { super.release(); selectionHolder.removeValueChangeListener(selectionChangeHandler); selectionIndexHolder.removeValueChangeListener(selectionIndexChangeHandler); selectionHolder = null; selectionIndexHolder = null; oldSelection = null; } // Helper Code *********************************************************** private E getSafeElementAt(int index) { return index < 0 || index >= getSize() ? null : getElementAt(index); } /** * Returns the index in the list of the first occurrence of the specified * element, or -1 if the element is {@code null} or the list does not * contain this element.

* * {@code null} is mapped to -1, because the current implementation * interprets a null selection as unspecified. * * @param element the element to search for * @return the index in the list of the first occurrence of the * given element, or -1 if the element is {@code null} or * the list does not contain this element. */ private int indexOf(Object element) { return indexOf(getListHolder().getValue(), element); } /** * Returns the index in the list of the first occurrence of the specified * element, or -1 if the element is {@code null} or the list does not * contain this element.

* * {@code null} is mapped to -1, because the current implementation * interprets a null selection as unspecified. * * @param aList the List or ListModel used to look up the element * @param element the element to search for * @return the index in the list of the first occurrence of the * given element, or -1 if the element is {@code null} or * the list does not contain this element. */ private int indexOf(Object aList, Object element) { if (element == null) { return NO_SELECTION_INDEX; } else if (getSize(aList) == 0) { return NO_SELECTION_INDEX; } if (aList instanceof List) { return ((List) aList).indexOf(element); } // Search the first occurrence of element in the list model. ListModel listModel = (ListModel) aList; int size = listModel.getSize(); for (int index = 0; index < size; index++) { if (element.equals(listModel.getElementAt(index))) { return index; } } return NO_SELECTION_INDEX; } /** * Sets the index according to the selection, unless the selection * is {@code null}. * Also initializes the copied selection and selection index. * This method is invoked by the constructors to synchronize * the selection and index. No listeners are installed yet.

* * An initial selection of {@code null} may indicate that the selection * is unspecified. This happens for example, if the selection holder * adapts a bean property via a PresentationModel, but the bean * is {@code null}. In this case, the current semantics decides to not * set the selection index - even if null is a list element.

* * This leads to an inconsistency. If we construct a SelectionInList * with {1, 2, 3} and initial selection 1, the selection index is set. * If we construct {null, 2, 3} and initial selection null, the selection * index is not set.

* * TODO: Discuss whether we want to set the selection index if the * initial selection is {@code null}. */ private void initializeSelectionIndex() { E selectionValue = (E) selectionHolder.getValue(); if (selectionValue != null) { setSelectionIndex(indexOf(selectionValue)); } oldSelection = selectionValue; oldSelectionIndex = getSelectionIndex(); } // Overriding Superclass Behavior ***************************************** /** * Creates and returns the ListDataListener used to observe * changes in the underlying ListModel. It is re-registered * in #updateListModel. * * @return the ListDataListener that handles changes * in the underlying ListModel */ @Override protected ListDataListener createListDataChangeHandler() { return new ListDataChangeHandler(); } /** * Removes the list data change handler from the old list in case * it is a ListModel and adds it to new one in case * it is a ListModel. * It then fires a property change for the list and a contents change event * for the list content. Finally it tries to restore the previous selection * - if any.

* * Since version 1.1 the selection will be restored after * the list content change has been indicated. This is because some * listeners may clear the selection in a side-effect. * For example a JTable that is bound to this SelectionInList * via an AbstractTableAdapter and a SingleSelectionAdapter * will clear the selection if the new list has a size other * than the old list. * * @param oldList the old list content * @param oldSize the size of the old List content * @param newList the new list content * * @see javax.swing.JTable#tableChanged(javax.swing.event.TableModelEvent) */ @Override protected void updateList(Object oldList, int oldSize, Object newList) { boolean hadSelection = hasSelection(); Object oldSelectionHolderValue = hadSelection ? getSelectionHolder().getValue() : null; super.updateList(oldList, oldSize, newList); if (hadSelection) { setSelectionIndex(indexOf(newList, oldSelectionHolderValue)); } } // Event Handlers ********************************************************* /** * Handles ListDataEvents in the list model. * In addition to the ListDataChangeHandler in IndirectListModel, * this class also updates the selection index. */ private final class ListDataChangeHandler implements ListDataListener { /** * Sent after the indices in the index0, index1 * interval have been inserted in the data model. * The new interval includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalAdded(ListDataEvent evt) { int index0 = evt.getIndex0(); int index1 = evt.getIndex1(); int index = getSelectionIndex(); fireIntervalAdded(index0, index1); // If the added elements are after the index; do nothing. if (index >= index0) { setSelectionIndex(index + index1 - index0 + 1); } } /** * Sent after the indices in the index0, index1 interval * have been removed from the data model. The interval * includes both index0 and index1. * * @param evt a ListDataEvent encapsulating the * event information */ public void intervalRemoved(ListDataEvent evt) { int index0 = evt.getIndex0(); int index1 = evt.getIndex1(); int index = getSelectionIndex(); fireIntervalRemoved(index0, index1); if (index < index0) { // The removed elements are after the index; do nothing. } else if (index <= index1) { setSelectionIndex(NO_SELECTION_INDEX); } else { setSelectionIndex(index - (index1 - index0 + 1)); } } /** * Sent when the contents of the list has changed in a way * that's too complex to characterize with the previous * methods. For example, this is sent when an item has been * replaced. Index0 and index1 bracket the change. * * @param evt a ListDataEvent encapsulating the * event information */ public void contentsChanged(ListDataEvent evt) { fireContentsChanged(evt.getIndex0(), evt.getIndex1()); updateSelectionContentsChanged(evt.getIndex0(), evt.getIndex1()); } private void updateSelectionContentsChanged(int first, int last) { if (first < 0) { return; } int selectionIndex = getSelectionIndex(); if (first <= selectionIndex && selectionIndex <= last) { // need to synch directly on the holder because the // usual methods for setting selection/-index check for // equality getSelectionHolder().setValue(getElementAt(selectionIndex)); } } } /** * Listens to changes of the selection. */ private final class SelectionChangeHandler implements PropertyChangeListener { /** * The selection has been changed. Updates the selection index holder's * value and notifies registered listeners about the changes - if any - * in the selection index, selection empty, selection, and value.

* * Adjusts the selection holder's value and the old selection index * before any event is fired. This ensures that the event old and * new values are consistent with the SelectionInList's state.

* * The current implementation assumes that the event sources * provides a non-{@code null} new value. An arbitrary selection holder * may fire change events where the new and/or old value is * {@code null} to indicate that it is unknown, unspecified, * or difficult to compute (now).

* * TODO: Consider getting the new selection safely from the selection * holder in case the new value is {@code null}. See the commented * code section below. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { E oldValue = (E) evt.getOldValue(); E newSelection = (E) evt.getNewValue(); // if (newSelection == null) { // newSelection = (E) selectionHolder.getValue(); // } int newSelectionIndex = indexOf(newSelection); if (newSelectionIndex != oldSelectionIndex) { selectionIndexHolder.removeValueChangeListener(selectionIndexChangeHandler); selectionIndexHolder.setValue(Integer.valueOf(newSelectionIndex)); selectionIndexHolder.addValueChangeListener(selectionIndexChangeHandler); } int theOldSelectionIndex = oldSelectionIndex; oldSelectionIndex = newSelectionIndex; oldSelection = newSelection; firePropertyChange(PROPERTYNAME_SELECTION_INDEX, theOldSelectionIndex, newSelectionIndex); firePropertyChange(PROPERTYNAME_SELECTION_EMPTY, theOldSelectionIndex == NO_SELECTION_INDEX, newSelectionIndex == NO_SELECTION_INDEX); /* * Implementation Note: The following two lines fire the * PropertyChangeEvents for the 'selection' and 'value' properties. * If the old and new value are equal, no event is fired. * * TODO: Consider using ==, not equals to check for changes. * That would enable API users to use the selection holder with * beans that must be checked with ==, not equals. * However, the SelectionInList's List would still use equals * to find the index of an element. */ firePropertyChange(PROPERTYNAME_SELECTION, oldValue, newSelection); fireValueChange(oldValue, newSelection); } } /** * Listens to changes of the selection index. */ private final class SelectionIndexChangeHandler implements PropertyChangeListener { /** * The selection index has been changed. Updates the selection holder * value and notifies registered listeners about changes - if any - * in the selection index, selection empty, selection, and value.

* * Handles null old values in the index PropertyChangeEvent. * Ignores null new values in this events, because the selection * index value must always be a non-null value.

* * Adjusts the selection holder's value and the old selection index * before any event is fired. This ensures that the event old and * new values are consistent with the SelectionInList's state. * * @param evt the property change event to be handled */ public void propertyChange(PropertyChangeEvent evt) { int newSelectionIndex = getSelectionIndex(); E theOldSelection = oldSelection; //E oldSelection = getSafeElementAt(oldSelectionIndex); E newSelection = getSafeElementAt(newSelectionIndex); /* * Implementation Note: The following conditional suppresses * value change events if the old and new selection are equal. * * TODO: Consider using ==, not equals to check for changes. * That would enable API users to use the selection holder with * beans that must be checked with ==, not equals. * However, the SelectionInList's List would still use equals * to find the index of an element. */ if (!Objects.equals(theOldSelection, newSelection)) { selectionHolder.removeValueChangeListener(selectionChangeHandler); selectionHolder.setValue(newSelection); selectionHolder.addValueChangeListener(selectionChangeHandler); } int theOldSelectionIndex = oldSelectionIndex; oldSelectionIndex = newSelectionIndex; oldSelection = newSelection; firePropertyChange(PROPERTYNAME_SELECTION_INDEX, theOldSelectionIndex, newSelectionIndex); firePropertyChange(PROPERTYNAME_SELECTION_EMPTY, theOldSelectionIndex == NO_SELECTION_INDEX, newSelectionIndex == NO_SELECTION_INDEX); firePropertyChange(PROPERTYNAME_SELECTION, theOldSelection, newSelection); fireValueChange(theOldSelection, newSelection); } } } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/list/ObservableList.java0000644000175000017500000000527411374522114027143 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.list; import java.util.List; import javax.swing.ListModel; /** * Combines the {@link List} and {@link ListModel} interfaces. * Useful to specify a type that operates like a List and is published * as a ListModel so it can be bound to user interface components * such as {@code JList}, {@code JTable} and {@code JComboBox}.

* * The JGoodies Binding ships with two predefined implementations: * {@link ArrayListModel} and {@link LinkedListModel}.

* * See also the class comment in {@link SelectionInList} that discusses * the advantages you gain if you add {@code ListModel} capabilities * to a {@code List} * * @author Karsten Lentzsch * @version $Revision: 1.11 $ * * @param the type of the list elements * * @deprecated Replaced by {@link com.jgoodies.common.collect.ObservableList}. * This class will be removed from the next library version. */ @Deprecated public interface ObservableList extends com.jgoodies.common.collect.ObservableList { // This interface just extends the new ObservableList. } jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/list/package.html0000644000175000017500000000520311374522114025631 0ustar twernertwerner Contains classes that operate on, hold and observe lists and list models. The observer mechanism is based on the ListModel interface that describes how to observe changes in the content and structure of a list.

Related Documentation

For more information see: @see com.jgoodies.binding @see com.jgoodies.binding.adapter @see com.jgoodies.binding.beans @see com.jgoodies.binding.formatter @see com.jgoodies.binding.value jgoodies-binding-2.1.0/src/core/com/jgoodies/binding/list/ArrayListModel.java0000644000175000017500000000670311374522114027114 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.list; import java.util.Collection; /** * Adds {@link javax.swing.ListModel} capabilities to its superclass * {@code ArrayList}, i. e. allows to observe changes in the content and * structure. Useful for lists that are bound to list views, for example * JList, JComboBox and JTable. * * @author Karsten Lentzsch * @version $Revision: 1.12 $ * * @param the type of the list elements * * @deprecated Replaced by {@link com.jgoodies.common.collect.ArrayListModel}. * This class will be removed from the next library version. */ @Deprecated public final class ArrayListModel extends com.jgoodies.common.collect.ArrayListModel implements ObservableList { // Instance Creation ****************************************************** /** * Constructs an empty list with an initial capacity of ten. */ public ArrayListModel() { this(10); } /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list. * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayListModel(int initialCapacity) { super(initialCapacity); } /** * Constructs a list containing the elements of the specified collection, * in the order they are returned by the collection's iterator. * The ArrayListModel instance has an initial capacity of * 110% the size of the specified collection. * * @param c the collection whose elements are to be placed into this list. * @throws NullPointerException if the specified collection is * {@code null} */ public ArrayListModel(Collection c) { super(c); } } jgoodies-binding-2.1.0/src/extras/0000755000175000017500000000000011374522114016760 5ustar twernertwernerjgoodies-binding-2.1.0/src/extras/com/0000755000175000017500000000000011374522114017536 5ustar twernertwernerjgoodies-binding-2.1.0/src/extras/com/jgoodies/0000755000175000017500000000000011374522114021341 5ustar twernertwernerjgoodies-binding-2.1.0/src/extras/com/jgoodies/binding/0000755000175000017500000000000011374522114022753 5ustar twernertwernerjgoodies-binding-2.1.0/src/extras/com/jgoodies/binding/extras/0000755000175000017500000000000011374522114024261 5ustar twernertwernerjgoodies-binding-2.1.0/src/extras/com/jgoodies/binding/extras/DelayedWriteValueModel.java0000644000175000017500000002374511374522114031477 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.extras; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.Timer; import com.jgoodies.binding.beans.DelayedPropertyChangeHandler; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.DelayedReadValueModel; import com.jgoodies.binding.value.ValueModel; /** * A ValueModel that deferres write-access for a specified delay. * Useful to coalesce frequent changes. For example if a heavy computation * shall be performed only for a "stable" selection after a series of * quick selection changes.

* * Wraps a given subject ValueModel and returns the subject value or * the value to be set as this model's value. Observes subject value changes * and forwards them to listeners of this model. If a value is set to this * model, a Swing Timer is used to delay the value commit to the subject. * A previously started timer - if any - will be stopped before.

* * TODO: Describe how and when listeners get notified about the delayed change.

* * TODO: Write about the recommended delay time - above the double-click time * and somewhere below a second, e.g. 100ms to 200ms.

* * TODO: Write about a slightly different commit handling. The current * implementation defers the commit until the value is stable for the * specified delay; it's a DelayUntilStableForXXXmsValueModel. Another * feature is to delay for a specified time but ensure that some commits * and change notifications happen. The latter is a CoalescingWriteValueModel.

* * TODO: Summarize the differences between the DelayedReadValueModel, this * DelayedWriteValueModel, and the DelayedPropertyChangeHandler. * * @author Karsten Lentzsch * @version $Revision: 1.16 $ * * @see DelayedReadValueModel * @see DelayedPropertyChangeHandler * @see javax.swing.Timer */ public final class DelayedWriteValueModel extends AbstractValueModel { /** * Refers to the underlying subject ValueModel. */ private final ValueModel subject; /** * The Timer used to perform the delayed commit. */ private final Timer timer; /** * If {@code true} all pending updates will be coalesced. * In other words, an update will be fired if no updates * have been received for this model's delay. */ private boolean coalesce; /** * Holds the most recent pending value. It is updated * everytime #setValue is invoked. */ private Object pendingValue = new Integer(1967); // Instance Creation ****************************************************** /** * Constructs a DelayedWriteValueModel for the given subject ValueModel * and the specified Timer delay in milliseconds with coalescing disabled. * * @param subject the underlying (or wrapped) ValueModel * @param delay the milliseconds to wait before a change * shall be committed * * @throws IllegalArgumentException if the delay is negative */ public DelayedWriteValueModel(ValueModel subject, int delay) { this(subject, delay, false); } /** * Constructs a DelayedWriteValueModel for the given subject ValueModel * and the specified Timer delay in milliseconds with coalescing disabled. * * @param subject the underlying (or wrapped) ValueModel * @param delay the milliseconds to wait before a change * shall be committed * @param coalesce {@code true} to coalesce all pending changes, * {@code false} to fire changes with the delay when an update * has been received * * @throws IllegalArgumentException if the delay is negative * * @see #setCoalesce(boolean) */ public DelayedWriteValueModel(ValueModel subject, int delay, boolean coalesce) { this.subject = subject; this.coalesce = coalesce; this.timer = new Timer(delay, new ValueCommitListener()); timer.setRepeats(false); subject.addValueChangeListener(new SubjectValueChangeHandler()); } // ValueModel Implementation ****************************************** /** * Returns the subject's value or in case of a pending commit, * the pending new value. * * @return the subject's current or future value. */ public Object getValue() { return isPending() ? pendingValue : subject.getValue(); } /** * Sets the given new value after this model's delay. * Does nothing if the new value and the latest pending value are the same. * * TODO: Describe how and when listeners get notified about the * delayed value change. * * @param newValue the value to set */ public void setValue(Object newValue) { if (newValue == pendingValue) { return; } pendingValue = newValue; if (coalesce) { timer.restart(); } else { timer.start(); } } // Accessors ************************************************************** /** * Returns the delay, in milliseconds, that is used to defer value commits. * * @return the delay, in milliseconds, that is used to defer value commits * * @see #setDelay */ public int getDelay() { return timer.getDelay(); } /** * Sets the delay, in milliseconds, that is used to defer value commits. * * @param delay the delay, in milliseconds, that is used to defer value commits * @see #getDelay */ public void setDelay(int delay) { timer.setInitialDelay(delay); timer.setDelay(delay); } /** * Returns if this model coalesces all pending changes or not. * * @return {@code true} if all pending changes will be coalesced, * {@code false} if pending changes are fired with a delay * when an update has been received. * * @see #setCoalesce(boolean) */ public boolean isCoalesce() { return coalesce; } /** * Sets if this model shall coalesce all pending changes or not. * In this case, a change event will be fired first, * if no updates have been received for this model's delay. * If coalesce is {@code false}, a change event will be fired * with this model's delay when an update has been received.

* * The default value is {@code false}.

* * Note that this value is not the #coalesce value * of this model's internal Swing timer. * * @param b {@code true} to coalesce, * {@code false} to fire separate changes */ public void setCoalesce(boolean b) { coalesce = b; } // Misc ******************************************************************* /** * Stops a running timer. Pending changes - if any - are canceled * and won't be performed by the ValueUpdateListener. * * @since 1.2 */ public void stop() { timer.stop(); } /** * Checks and answers whether this model has one or more pending changes. * * @return {@code true} if there are pending changes, {@code false} if not. * * @since 2.0.4 */ public boolean isPending() { return timer.isRunning(); } // Event Handling ***************************************************** /** * Describes the delayed action to be performed by the timer. */ private final class ValueCommitListener implements ActionListener { /** * An ActionEvent has been fired by the Timer after its delay. * Commits the pending value and stops the timer. */ public void actionPerformed(ActionEvent e) { subject.setValue(pendingValue); stop(); } } /** * Forwards value changes in the subject to listeners of this model. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { fireValueChange(evt.getOldValue(), evt.getNewValue(), true); } } } jgoodies-binding-2.1.0/src/extras/com/jgoodies/binding/extras/NonNullValueModel.java0000644000175000017500000001212411374522114030467 0ustar twernertwerner/* * Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.binding.extras; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import com.jgoodies.binding.value.AbstractValueModel; import com.jgoodies.binding.value.ValueModel; /** * A {@link ValueModel} implementation that avoids {@code null} values. * It wraps another ValueModel and returns a (non-null) default value * if the wrapped ValueModel returns null.

* * Note that value change events fired by this model may use null * as old and/or new value. This is because PropertyChangeEvents use null * to indicate that the old and/or new value is not provided by the event.

* * Note: This class is not yet part of the binary Binding * library; it comes with the Binding distributions as an extra. * The API is work in progress and may change without notice; * this class may even be completely removed from future distributions. * If you want to use this class, you may consider copying it into * your code base. * * @author Karsten Lentzsch * @version $Revision: 1.10 $ * * @since 1.1 */ public final class NonNullValueModel extends AbstractValueModel { /** * Holds the wrapped subject ValueModel. */ private final ValueModel subject; /** * The value returned by this model whenever the * underlying (wrapped) ValueModel returns {@code null}. */ private final Object defaultValue; // Instance Creation ****************************************************** /** * Constructs an NonNullValueModel for the given ValueModel. * * @param subject the underlying (or wrapped) ValueModel * @param defaultValue the value used whenever the wrapped model * returns {@code null} * * @throws NullPointerException if the subject or defaultValue is {@code null} */ public NonNullValueModel(ValueModel subject, Object defaultValue) { this.subject = subject; this.defaultValue = checkNotNull(defaultValue, "The default value must not be null."); subject.addValueChangeListener(new SubjectValueChangeHandler()); } // ValueModel Implementation ********************************************** /** * Returns this model's current subject value. * * @return this model's current subject value. */ public Object getValue() { Object subjectValue = subject.getValue(); return subjectValue != null ? subjectValue : defaultValue; } /** * Sets the given value to the wrapped ValueModel. * The value set can be {@code null}. * * @param newValue the value to set */ public void setValue(Object newValue) { subject.setValue(newValue); } // Event Handling ********************************************************* /** * Forwards value changes in the subject to listeners of this model. * We don't use the default value if the old and/or new value is null. * This is because null has a different meaning in PropertyChangeEvents: * it indicates that the value is not available. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { fireValueChange(evt.getOldValue(), evt.getNewValue(), true); } } } jgoodies-binding-2.1.0/src/extras/com/jgoodies/binding/extras/package.html0000644000175000017500000000544511374522114026552 0ustar twernertwerner Contains optional classes that ship only with the source distribution. The API of these classes is work in progress and may change without notice. The classes may even be completely removed from future versions of the Binding library. If you want to use a class from this package, you may consider copying it into your code base.

Related Documentation

For more information see: @see com.jgoodies.binding.adapter @see com.jgoodies.binding.beans @see com.jgoodies.binding.list @see com.jgoodies.binding.value jgoodies-binding-2.1.0/default.properties0000644000175000017500000000571411374522114020434 0ustar twernertwerner# ------------------------------------------------------------- # DEFAULT JGOODIES BINDING BUILD PROPERTIES # ------------------------------------------------------------- # # DO NOT EDIT THIS FILE IN ORDER TO CUSTOMIZE BUILD PROPERTIES. # CREATE AND EDIT build.properties FILE INSTEAD. # # Project Properties ------------------------------------------ Name=JGoodies Binding shortname=binding name=jgoodies-${shortname} spec.version=2.1 impl.version=2.1.0 version.name=2.1.0 dist.version=2_1_0-20100518 spec.title=${Name} API Specification impl.title=${Name} spec.vendor=JGoodies Karsten Lentzsch impl.vendor=JGoodies Karsten Lentzsch copyright.date=2002-2010 copyright.owner=JGoodies Karsten Lentzsch copyright.message=Copyright © ${copyright.date} ${copyright.owner}. All Rights Reserved. top.dir=${basedir} # Source Properties ------------------------------------------- src.dir = ${top.dir}/src src.core.dir = ${src.dir}/core src.extras.dir = ${src.dir}/extras src.test.dir = ${src.dir}/test docs.dir = ${top.dir}/docs # Library Properties ------------------------------------------ lib.dir = ${top.dir}/lib lib.common.jar = ${lib.dir}/jgoodies-common-1.0.0.jar # JavaDoc Properties ------------------------------------------ javadoc.link=http://java.sun.com/j2se/1.5/docs/api/ javadoc.packages=com.jgoodies.binding.* javadoc.overview=${src.core.dir}/overview.html # Build Properties -------------------------------------------- build.compiler.pedantic=false build.compile.debug=on build.compile.deprecation=off build.compile.fork=no build.compile.nowarn=on build.compile.source=1.5 build.compile.target=1.5 build.encoding=ISO-8859-1 build.dir = ${top.dir}/build build.classes.dir = ${build.dir}/classes build.core.dir = ${build.classes.dir}/core build.extras.dir = ${build.classes.dir}/extras build.test.dir = ${build.classes.dir}/test build.docs.dir = ${build.dir}/docs build.javadocs.dir = ${build.docs.dir}/api build.reports.dir = ${build.dir}/test-reports build.core.jar = ${build.dir}/${name}.jar build.maven.dir = ${build.dir}/maven build.maven.pom.template = ${top.dir}/conf/maven-pom-template.xml # Dist Properties ----------------------------------------------- dist.root.dir = ${top.dir}/dist dist.name = ${name}-${impl.version} deploy.name = ${name}-${dist.version} dist.subdir = ${dist.name} dist.dir = ${dist.root.dir}/${dist.subdir} dist.zip = ${dist.root.dir}/${deploy.name}.zip dist.docs.dir = ${dist.dir}/docs dist.lib.dir = ${dist.dir}/lib dist.src.dir = ${dist.dir}/src dist.core.jar = ${dist.dir}/${dist.name}.jar dist.maven.pom = ${build.maven.dir}/pom.xml dist.maven.bin.jar = ${build.maven.dir}/${dist.name}.jar dist.maven.src.jar = ${build.maven.dir}/${dist.name}-sources.jar dist.maven.bundle = ${dist.root.dir}/${dist.name}-bundle.jar jgoodies-binding-2.1.0/build.xml0000644000175000017500000003222611374522114016511 0ustar twernertwerner jgoodies-binding-2.1.0/LICENSE.txt0000644000175000017500000000323411374522114016510 0ustar twernertwerner The BSD License for the JGoodies Binding ======================================== Copyright (c) 2002-2010 JGoodies Karsten Lentzsch. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: o Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. o Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. o Neither the name of JGoodies Karsten Lentzsch nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jgoodies-binding-2.1.0/lib/0000755000175000017500000000000011374522114015431 5ustar twernertwernerjgoodies-binding-2.1.0/RELEASE-NOTES.txt0000644000175000017500000006711011374522114017377 0ustar twernertwerner JGoodies Data Binding Version 2.1.0 Release Notes INTRODUCTION This maintenance release uses and requires the JGoodies Common lib. Make sure that you include jgoodies-common-1.0.0.jar in your classpath when using this Binding version. CHANGES THAT AFFECT THE COMPATIBILITY o BasicComponentFactory#createCheckBox and #createRadioButton now accept a marked text that may contain a mnemonic marker ('&') to mark the mnemonic and its index. See MnemonicUtils for details about this configuration. Also, the content area of the returned components is now not filled. This is typically, if a check box or radio button is contained in a panel with a gradient background, for example tabbed panes in many Windows L&f configurations. o Removed EmptyDateFormatter and EmptyNumberFormatter. These have been replaced by the new JGoodies Common classes EmptyDateFormat and EmptyNumberFormat. Adjusted the BasicComponentFactory to use the new classes. BUG FIXES #134: Binding via Bindings looses AbstractButton properties. OTHER CHANGES o Marked ArrayListModel, LinkedListModel, and ObservableList as deprecated. These classes have been replaced by new versions from the JGoodies Common library. The Binding classes will be removed from the next Binding version. o Fixed a few JavaDocs. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 2.0.6 Release Notes INTRODUCTION This maintenance release fixes a regression introduced by the parameter strings for ValueModels created by BeanAdapters and PropertyAdapters. The #toString method of a ValueModel returned by a Bean- or PropertyAdapter on a null bean failed with an NPE. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 2.0.5 Release Notes INTRODUCTION This maintenance release reverts a change made in the Binding 2.0.4 that lead to an incompatible serialization of Model subclasses. 2.0.4 introduced a feature in the ExtendedPropertyChangeSupport to ensure that listeners are notified in the event dispatch thread. This feature was unused and has now been removed to make version 2.0.5 binary compatible with versions 2.0.0, 2.0.1, 2.0.2, and 2.0.3. CHANGES o Source code improvement: uses arrays for copying listener lists. o Replaced LinkedList by ArrayList. o Updated the Forms library used for the tutorial to 1.3.0 pre1. o Slightly overhauled the visual design of the tutorial screens. Added mnemonics and colons where, replaced some titles and titled separators by labels, removed unnecessary separators. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 2.0.4 Release Notes INTRODUCTION This maintenance release adds a public API to the delaying classes to check whether there are one or more pending events. The #release methods in PresentationModel, BeanAdapter, PropertyAdapter, TextComponentConnector, and PropertyConnector can be called multiple times (issue #108). CHANGES o Added #isPending to DelayedPropertyChangeHandler, DelayedReadValueModel and DelayedWriteValueModel. o More final methods in DelayedPropertyChangeHandler. o Added tests for calling release multiple times. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 2.0.3 Release Notes INTRODUCTION This maintenance release improves the debug output for subclasses of AbstractValueModel and ships improved tutorial sources and slightly improved JavaDocs. CHANGES o Added AbstractValueModel#paramString and #valueString. o Implemented #paramString in BufferedValueModel, PropertyAdapter, and BeanAdapter.SimplePropertyAdapter. o Updated the Forms library used for the tutorial to 1.2.0. o The download link in the HTML pages points to JGoodies.com. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 2.0.2 Release Notes INTRODUCTION This maintenance release includes minor improvements. OTHER CHANGES #127: PresentationModel constructors shall use the bean type. Changed PresentationModel(Object) and PresentationModel(Object, ValueModel) to PresentationModel(B) and PresentationModel(B, ValueModel). #128: Make AbstractConverter#release non-final o PropertyConnector always reads the new value, if the property name is null. o Simpler generic types in some tutorial examples. o Updated copyright information. o Updated the Forms library used for the tutorial to 1.2b2. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 2.0.1 Release Notes INTRODUCTION This maintenance release fixes a minor bug in the SelectionInList. BUGS FIXED #122: Broken SelectionInList#setSelectionIndex upper bound check OTHER CHANGES #123: Improved the SelectionInList JavaDocs regarding null selections if the List or ListModel contains null. o Added Bindings#flushImmediately() and #isFocusOwnerBuffering(). o Corrected the IndirectListModel documentation. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 2.0.0 Release Notes INTRODUCTION With this major update the JGoodies Binding has been moved to Java 5. Key classes and methods have been generified, sources use annotations, and the code uses the enhanced for loop. This version is binary incompatible with previous versions. CHANGES THAT AFFECT THE COMPATIBILITY o Replaced the 1.5 SelectionInList and SelectionInListModel by the 1.4 SelectionInList. Replaced ListHolder, ListModelHolder by IndirectListModel. o Added #getList() to SelectionInList and IndirectListModel. o SelectionInList#getListModel throws a ClassCastException if the list holder doesn't hold a ListModel. o Generified the following classes: ArrayListModel, LinkedListModel, SelectionInList, IndirectListModel, AbstractTableAdapter, PresentationModel, BeanAdapter, and PropertyAdapter. o PropertyConnector constructor is private, use #connect instead. o PropertyConnector#connect returns the PropertyConnector. o Bindings#bind(JComboBox, SelectionInList) and #bind(JList, SelectionInList) synchronize the component properties "visible" and "enabled" with the SelectionInList's selection holder, if this is a ComponentValueModel. BUGS FIXED #119: SelectionInList may fire wrong ListDataEvents. NEW FEATURES o Added PropertyConnector#connectAndUpdate(ValueModel, Object, String) o AbstractTableAdapter#getRow is public o AbstractTableAdapter constructor with column names uses varargs. OTHER CHANGES o Source code cleanup: enhanced for loop, annotations, marked private fields as final, removed trailing blanks. o Fixed JavaDoc typos. HOW TO MIGRATE FROM VERSION 1.4 TO 2.0 o Add type information to ArrayListModels, LinkedListModels, SelectionInLists, SelectionInListModels, PresentationModels, BeanAdapters, PropertyAdapters, AbstractTableAdapters. o Remove unnecessary type casts. o Replace PropertyConnector constructor calls by PropertyConnector#connect or #connectAndUpdate. CHANGES PLANNED FOR FUTURE VERSIONS o ExtendedPropertyChangeSupport shall: + provide an optional weak registration of listeners, + an option to notify listeners in the event-dispatch-thread, + add debug features to detect inconsistencies between a bean's property names and the names used to register a listener or used to fire a property change. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.5.0 Release Notes INTRODUCTION This version is the first step towards the generified Binding 2.0. It is binary incompatible with previous versions. Stable extras have been moved to the core, and the SelectionInList has changed. BUGS FIXED #118: BeanAdapter#getValue, #setValue bypass internal adapter. NEW FEATURES o SelectionInListModel is now in the core. The Bindings class and the BasicComponentFactory have been extended to bind or create JComboBoxes and JLists from SelectionInListModel. o The old SelectionInList that worked with List and ListModel has been replaced by a new SelectionInList that works with List only. o Added DelayedPropertyChangeHandler#getDelay and #setDelay. o Moved the ChangeTracker and LoggingUtils from extras to core. HOW TO MIGRATE TO VERSION 1.5 o For every 1.4 SelectionInList you should check whether it works with a ListModel vs. List or Array. In the first case, use a SelectionInListModel; for List and Arrays you can keep the SelectionInList. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.4.0 Release Notes INTRODUCTION This release comes with a behavior change in the PropertyConnector and extends the API to make it easier to subclass the BeanAdapter. This version is binary compatible with the 1.3.x versions, but the changed behavior was worth a new minor version number. However, most API users won't notice the difference and will just benefit from the PropertyConnector change. The PropertyConnector change improves a nasty situation with JFormattedTextFields that fire unnecessary "value" change events, if a user clicks in a formatted text field without changing it. If the value is null, a bean property connected to such a field will likely fire a property change event, because null may indicate a null value or 'unknown'. The default binding for JFormattedTextField is implemented with the PropertyConnector. The new PropertyConnector reads a target value and compares it with the value to be set. In case these are identical, no operation will be performed, and no change event will be fired. This addresses the case where the JFormattedTextField sends a change event from null to null. And it avoids unnecessary events in other situations too. NEW FEATURES o BeanAdapter.SimplePropertyAdapter not final. o BeanAdapter.SimplePropertyAdapter constructor protected as well as #fireChange and #setBean0 o Added BeanAdapter#createPropertyAdapter. OTHER CHANGES #117: PropertyConnector shall not fire unnecessary change events o Updated the Forms library used for the tutorial to version 1.1. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.3.2 Release Notes INTRODUCTION This release fixes a bug in ListModel implementations and a regression with the PropertyConnector and read-only properties. BUGS FIXED #115: ListDataEvents inconsistent with the ListModel #116: PropertyConnector may fail with read-only property ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.3.1 Release Notes INTRODUCTION This release changes the behavior of the PropertyConnector in the case that a target bean setter modifies the value set, or completely rejects the new value (issue #114). For example, a bean setter may trim a String or turn it into upper case. If setters don't modify or reject values, the old and new behavior are almost identical. The difference is in an extra read operation on the target bean side, that is used to compare the value set with the (potentially modified ) target value. The new behavior is now consistent with the TextComponentConnector that honors subject value modifications too. OTHER CHANGES o Removed the deprecated BeanUtils#getPropertyDescriptor methods. o Added a test case that the SelectionInList handles vetos in a custom selection index holder. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.3.0 Release Notes INTRODUCTION This release contains a few binary incompatible changes. CHANGES THAT AFFECT THE BINARY COMPATIBILITY o Removed the deprecated DocumentAdapter that has been replaced by the TextComponentConnector in version 1.2. o Removed Bindings#bind(ValueModel, JTextArea, Document, boolean) and Bindings#bind(ValueModel, JTextField, Document, boolean) that have been marked as deprecated in version 1.2. NEW FEATURES #112: Poor bean channel construction in PresentationModel. Added PresentationModel#createBeanAdapter(ValueModel). PresentationModel subclasses may override this method to configure the bean adapter, for example whether it observes changes or not. o BeanAdapter is no longer final. o Added BeanAdapter#getBeanChannel. o Added Model#firePropertyChange(PropertyChangeEvent) that allows to fire IndexedPropertyChangeEvents in Java 5 or later. o Added Model#fireVetoableChange(PropertyChangeEvent). #109: Add AbstractConverter#release ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.2.0 Release Notes INTRODUCTION This release brings a few improvements and API extensions. NEW FEATURES 51: Added LoggingUtils to the source code extras. Reduces the effort required to log property changes. 98: Bindings#bind(JComponent, String, ValueModel) reduces the effort required to bind a component property to a ValueModel. 103: PresentationModel#release. Setting the bean to null is still the preferred method to release listeners. 104: SelectionInList#release, ListHolder#release, ListModelHolder#release, SpinnerToValueModelConnector#release, SelectionInList2#release SelectionInListModel#release Instead of calling these #release methods, you typically release the PresentationModel or BeanAdapter that has created the (Value)Models used in the above classes. 106: Bindings#commitImmediately() is useful to commit a pending edit. o DelayedPropertyChangeHandler#stop. o DelayedReadValueModel#stop. o DelayedWriteValueModel#stop. CHANGES THAT AFFECT THE BINARY COMPATIBILITY o Removed the deprecated Model#fireMulticastPropertyChange, which has been replaced by #fireMultiplePropertiesChanged in 1.0.3. CHANGES THAT AFFECT THE BEHAVIOR 101: Model's change support is non-transient. And so serializable listeners will be serialized with the observed Model. AbstractConverter uses a PropertyChangeListener instead of implementing PropertyChangeListener. Most Binding API users register listeners with the Binding classes, not the Model directly. And so, this change won't affect most users. The change affects only those who register listeners with a Model diretly. 105: After a text update the caret is now at position 0, where it was at the end of the text before. This is achieved by the new TextComponentAdapter. Bindings#bind(ValueModel, JTextArea, boolean) and Bindings#bind(ValueModel, JTextField, boolean) now use the new TextComponentAdapter. And these methods keep the text area's/text field's Document, where version 1.1.x used a new PlainDocument instead. 107: BeanAdapter#release removes all listeners from the bean that have been registered with #addBeanPropertyChangeListener. OTHER CHANGES o Marked DocumentAdapter as deprecated. This class will be removed from the JGoodies Binding version 1.3. o Marked Bindings#bind(ValueModel, JTextArea, Document, boolean) and Bindings#bind(ValueModel, JTextField, Document, boolean) as deprecated. These methods will be removed from the Binding version 1.3. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.1.2 Release Notes INTRODUCTION This release fixes a bug and brings minor source code improvements. BUGS FIXED o Issue 100: Deadlock in ExtendedPropertyChangeSupport with identity check enabled. NEW FEATURES The PropertyConnector can now connect beans that do not support bound properties. This is intended for the case where only one bean fires property change events. OTHER CHANGES o Marked several inner classes as final. o Added JavaDocs and fixed JavaDoc typos. o FAQ is now online at https://binding.dev.java.net/faq.html. o Added a reference to the JSR 295 - Beans Binding. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.1.1 Release Notes INTRODUCTION This maintenance release fixes issue #87 and bug #92. It is recommended to use version to 1.1.1. CHANGES THAT AFFECT THE COMPATIBILITY The changes affect the behavior, not the binary compatibility. o Since 1.1.1 the SelectionInList's default selection holder checks the identity. This allows to use it as bean channel in PresentationModel, BeanAdapter, PropertyAdapter. It may now fire more changes than the previous holder, that fired only if the old and new selection were not equal. o The fix for issue #92 may lead to more or less events fired by all AbstractConverter subclasses. BUGS FIXED o Issue 87: SelectionInList default selection holder shall check identity. o Issue 92: AbstractConverter converts null event values. NEW FEATURES Most BeanUtils methods allow to provide the bean type now, where the bean instance was used before. This makes it easier to use package private beans that implement a public interface. OTHER CHANGES o Changed the visibility for enclosed types to reduce the amount of synthetic accessors. o Fixed JavaDoc typos. o Minor source style improvements. o The ANT build can create a Maven bundle. ------------------------------------------------------------------------- Find below release notes for previous versions. JGoodies Data Binding Version 1.1 Release Notes INTRODUCTION This release fixes bugs and provides new features and classes. It is recommended to upgrade your library version to 1.1. The 1.1 API is slightly incompatible with previous versions. However, existing valid code will typically work as before. Most changes are sanity checks; they ensure that arguments comply with constraints that are described in the JavaDocs. The topics for the 1.1 release are: 1) Bug fixing and API clarification. 2) New distribution layout. 3) Move stable extra sources to the library core. 4) Improved support for PropertyVetoExceptions. 5) Improved support in the presentation model layer for operating on frequently used component state: enabled, editable, visible. This is implemented by the new class ComponentValueModel. Due to a timely rollout of the 1.1 final, other improvements planned for the 1.1. version have been moved to future releases. The work on version 1.2 will begin immediately, and the list handling classes available in the 1.1 preview builds will be moved back to the library core. CHANGES THAT AFFECT THE COMPATIBILITY o SelectionInList keeps the selection after list changes, even if the selection is cleared during the change. This happens for example when the SelectionInList is bound to a JTable that may - as a side effect - clear the selection in JTable#tableChanged (see also issue 85). o The methods Bindings#bind(JTextComponent, *) have been replaced by Bindings#bind(JTextArea, *) and #bind(JTextField, *). This clarifies that these methods were not intended (and not able) to bind JEditorPanes and JTextPanes. The changes below are sanity checks that have been added to verify that parameters comply with the constraints that are described in the JavaDocs. o BeanAdapter and PresentationModel methods that get a property name throw an NPE if the property name is null. o PresentationModel, BeanAdapter and PropertyAdapter throw an IllegalArgumentException if the bean channel is a ValueHolder that has the identityCheck feature disabled. o SelectionInList throws an IllegalArgumentException if the listHolder is a ValueModel that has the identity check feature disabled. o SelectionInList constructor throws a ClassCastException if the list holder content is neither a List nor a ListModel. BUGS FIXED o Issue 78: SelectionInList#EmptyListModel may cause memory leak. o Issue 79: PropertyConnector updates a read-only property. o Issue 82: Clarify interval of list model content changes. o Issue 85: SelectionInList loses selection when bound to a table. DISTRIBUTION CHANGES o Issue 74: The sources no longer ship as a source Zip archive. They now come in the directory structure used by the CVS. This makes it easier to build the distribution using ANT. If you want to attach the library sources in an IDE, point to folder 'src/core'. NEW FEATURES o Issue 73: Improve support for constrained bean properties: + PropertyAdapter#setVetoableValue(Object) + BeanAdapter#setVetoableValue(String, Object) + BeanAdapter.SimplePropertyAdapter#setVetoableValue(Object) o Issue 75: Add access to (buffered) values to PresentationModel: + PresentationModel#getValue(String) + PresentationModel#setValue(String, Object) + PresentationModel#setVetoableValue(String, Object) + PresentationModel#getBufferedValue(String) + PresentationModel#setBufferedValue(String, Object) o Issue 76: Move extras to the library core: + com.jgoodies.binding.adapter.SpinnerAdapterFactory + com.jgoodies.binding.adapter.SpinnerToValueModelConnector + com.jgoodies.binding.beans.DelayedPropertyChangeHandler + com.jgoodies.binding.value.AbstractVetoableValueModel + com.jgoodies.binding.value.DelayedReadValueModel o Issue 77: Provide component state in the PresentationModel. A ComponentValueModel provides bound properties for: enabled, visible, editable. The Bindings class can register a handler with the ComponentValueModel that updates the component's state automatically. And the PresentationModel has new factory methods that vend a plain or buffered ComponentValueModel. Added: + com.jgoodies.binding.value.ComponentValueModel + Bindings#addComponentPropertyHandler + Bindings#removeComponentPropertyHandler + PresentationModel#getComponentModel(String) + PresentationModel#getBufferedComponentModel(String) For an example see the ComponentValueModel class comment and the tutorial's ComponentValueModel example. This feature is implemented for text components, radio buttons and check boxes. Lists, tables, combos and color choosers bound using the Bindings class, ignore the ComponentValueModel. See also issue #86 in the Binding's issue tracker. o New classes for indirect access to Lists and ListModels: + ListHolder is a ListModel that holds a List in a ValueModel. + ListModelHolder is a ListModel that holds a ListModel in a ValueModel. o New Extra source: The NonNullValueModel converts null values in #getValue to a given default value. OTHER CHANGES o Improved and corrected JavaDocs. o Added tests for writing a constrained bean property. o Added a bunch of tests for SelectionInList list changes. o Minor Java source style improvements. o Fixed an NPE in the tutorial's ComponentsExample. jgoodies-binding-2.1.0/docs/0000755000175000017500000000000011374522114015613 5ustar twernertwernerjgoodies-binding-2.1.0/docs/api/0000755000175000017500000000000011374522062016366 5ustar twernertwernerjgoodies-binding-2.1.0/docs/guide/0000755000175000017500000000000011374522114016710 5ustar twernertwernerjgoodies-binding-2.1.0/docs/guide/valuemodels.html0000644000175000017500000001363311374522114022124 0ustar twernertwerner JGoodies Binding :: Guide :: Value Models
:: JGOODIES Looks :: Professional Swing Look&Feels

:: Guide :: Value Models ::

ValueModel Interface

The ValueModel interface describes a read-write value that is observable. You can read its value using #getValue(), you can write its value using #setValue(Object). And you can register a PropertyChangeListener that will be notified if the value changes.

Predefined ValueModel Implementations

ValueHolder is the minimal ValueModel implementation. It holds a single value that one can get and set from outside. And it keeps a list of PropertyChangeListeners.

The most frequently used ValueModel implementation is the PropertyAdapter and its variations. It converts a single (bound) Bean property into a ValueModel. Actually most PropertyAdapters are returned by PresentationModel#getModel, #getBufferedModel, and the BeanAdapter class.

The BufferedValueModel provides a means to buffer values, so changes are deferred until OK or Apply is pressed. For details see the Buffering task.

The ConverterFactory vends ValueModel implementations that convert types, for example Date to String.

Custom ValueModel Implementations

To minimize the effort required to implement the ValueModel interface, you can extend the AbstractValueModel. Custom converters will likely extend the AbstractConverter. See also the Type Conversion task.

Combining ValueModels

Since ValueModel describes a uniform interface to get and set values, they can be easily combined. They can be wrapped, connected, composed, etc. For example, the BufferedValueModel wraps any ValueModel to buffer the value until a trigger indicates that the value shall be committed or flushed.
(c) 2010 JGoodies
 
jgoodies-binding-2.1.0/docs/guide/domainadapters.html0000644000175000017500000001275511374522114022603 0ustar twernertwerner JGoodies Binding :: Guide :: Domain Adapters
:: JGOODIES Looks :: Professional Swing Look&Feels

:: Guide :: Domain Adapters ::

PresentationModel

The PresentationModel uses a BeanAdapter to convert multiple Bean properties into ValueModels. It provides two sets of methods to vend such models: #getModel for unbuffered models and #getBufferedModel for buffered models. See the JavaDoc class comment for details. PresentationModel is the most frequently used means to adapt Java Bean properties.

BeanAdapter

The BeanAdapter converts multiple Java Bean's properties into ValueModels. Optionally the adapter can observe changes in bound properties to forward the bean's PropertyChangeEvents. Basically the BeanAdapter does for multiple properties what the PropertyAdapter does for a single bean property. Typically you should use a PresentationModel, not a BeanAdapter.

PropertyAdapter

The PropertyAdapter converts a single Java Bean property into a ValueModel. The bean property must be a single value as described by the Java Bean Specification. Optionally the adapter can observe changes in bound properties as described in section 7.4 of the Bean specification.

Typically you should use a PresentationModel, not a PropertyAdapter. Only if you have internal adapters that you don't expose and if you adapt a single property, the PropertyAdapter is useful.

(c) 2010 JGoodies
 
jgoodies-binding-2.1.0/docs/guide/tasks.html0000644000175000017500000003252311374522114020730 0ustar twernertwerner JGoodies Binding :: Guide :: Tasks
:: JGOODIES Looks :: Professional Swing Look&Feels

:: Guide :: Tasks ::

Adapting Domain Properties

If the domain object properties are implemented as Java Bean properties, they can be converted to ValueModels using a PresentationModel, BeanAdapter, or PropertyAdapter. Typically you'll use a PresentationModel, in some cases the simpler BeanAdapter, and only if you want to convert a single property, you may consider using a PropertyAdapter.

Observing and Firing Property Changes

In most cases we want to automatically update the view of a bound domain property changes. More generally, we want to update a view if a bound value changes. Therefore we must be able to register with the bound value to get notified about changes. That's the second role of the ValueModels - besides reading and writing values.

The Java Bean standard describes a standard mechanism to report changes in property values and to listen to these changes, or in other words, to observe values. To make Bean properties bound, the bean class must provide a pair of methods to add and remove a PropertyChangeListener. We use the same mechanism and exactly the same events for all ValueModels in the JGoodies Binding.

To minimize the effort required to make your bean properties bound, you can extend the class Model that provides all the methods necessary to fire events and register listeners.

Converting Values to Views

To present a value with a Swing component, you can either set the value in the component, or you can provide a component model that will be used by the component to read and write values from/to. If possible we favor the latter approach and therefore offer a bunch of adapter classes that convert ValueModels into the model interface required by the different Swing components. For example, the RadioButtonAdapter is a model that converts a ValueModel so it can be used with a JRadioButton. And the DocumentAdapter converts a ValueModel into a Document implementation, for use in JTextField, JTextArea and all other JTextComponents.

The BasicComponentFactory can create Swing components that are bound to a ValueModel; the factory chooses the appropriate adapter. If you already have a component factory, or if you are using customized versions of the standard Swing components, you can use the more basic Bindings class. It establishes the connection between a ValueModel and a given Swing component by setting the appropriate ValueModel adapter as the component's model.

Buffering

A BufferedValueModel is used to hold a temporary copy of the value in another ValueModel (known as the subject). The application modifies the temporary copy, but the BufferedValueModel only gives this temporary value to its subject when the application confirms the changes. The application also has the option of canceling the changes, resetting the temporary copy to the subject's value.

For example, suppose the application provides a series of text fields for entering customer name, address, phone, etc., but we only want the Customer object to be updated after the user has finished entering data and has indicated completion by clicking on an OK button. This technique is often used in database applications, to postpone updating the customer record in the database until all changes to that record are completed. In this application, the customer's old address would likely be held by an PropertyAdapter on the Customer bean. The property adapter would become the subject of a BufferedValueModel. The BufferedValueModel would make a temporary copy of the customer's address and make that value available to the input field for editing. The user could change the address, but so far only the temporary copy has been altered. Only when the users clicks on 'OK' does the application notify each field's BufferedValueModel to replace the corresponding value in the Customer object.

A BufferedValueModel is constructed for given ValueModels for the subject and trigger channel. The subject is a ValueModel containing the data value. The trigger channel is a ValueHolder containing the boolean object false. Later, when the user clicks on 'OK', the application can cause the temporary copy to become the subject's value by setting the triggerChannel's value to true. The application can also cancel any edits, by setting the triggerChannel's value to false. Note that the prior value in the triggerChannel is not significant - as long as a value change is reported.

By using the same trigger channel for all of the BufferedValueModels, the application can cause them all to be updated at the same time. This is the usual arrangement for a set of related widgets.

Change Management

You may want to detect whether the user has changed an object in an editor to enable or disable a 'Save' action or an 'OK' button. Therefore you can compare the edited object's old and new values, or listen to object property changes, or observe the buffering state of BufferedValueModels - if you use any.

Comparing old and new values is the most precise approach to detect whether things changed or not. But it requires to duplicate the edited values and compare them later. You can copy (or clone) your domain objects and compare them after each change using a custom #equals implementation.

The classes PresentationModel and BeanAdapter provide a means to detect potential changes. They listen to all bean property changes and keep this state in a bound property changed. You can read, write and observe this state, for example to enable or disable Actions.

If you are buffering the edited values, you can listen to the BufferedValueModels buffering property to detect pending changes.

Indirection

Indirection is used to change the target object of a bound object property from one target to another. For example, if you display a list of albums, and present the selection in a details panel, you want to change the target object whenever the selection changes. In other words, the details components are bound to the property of an object, the indirection tells the binding which object it is.

If you are using PresentationModel, BeanAdapter or PropertyAdapter to convert Bean properties to ValueModels, you can set the current target bean in these adapters using #setBean(Object).

If multiple ValueModels shall be redirected from one target to another, you'll typically use a bean channel. That is another ValueModel that holds the target bean. And every interested party can observe changes in the bean channel to change its target. The PresentationModel, BeanAdapter and PropertyAdapter already provide constructors to use a bean channel.

Type Conversion

The ValueModel interface operates on general Object instances. If you want to convert types, you can wrap a ValueModel and perform the conversion while you get and set a value. In addition you must convert the old and new value in all PropertyChangeEvents fired by the wrapping ValueModel. Class AbstractConverter minimizes the effort required for such a conversion.

Conversions among ValueModels are rare, just because most conversions are required only for the presentation in the user interface. The latter can often be handled using the JFormattedTextField that provides a powerful means to format, parse, verify, and modify objects. See also the JavaDoc class comment in AbstractConverter.

Renaming Domain Properties

If you use Java Bean properties in the domain layer, there are no direct references from the model or presentation layer to the domain. To access properties the property's name and the accessor names must by synchronized. This makes renaming properties a little bit harder.

I recommend to use String constants, not Strings, for the property names. Then refer to these constants when getting ValueModels from a PresentationModel or when adding and removing listeners. Renaming a property is then a 2-step process: you rename 1) the getter and setter and 2) the property name constant.

Note that this renaming problem applies to code obfuscation too. Most code obfuscators rename the property accessor names, but leave the property name unchanged. Attempts to observe the property or to create an adapting ValueModel by the property name will then fail. Either you exclude the Bean accessors from the obfuscation, or you use ValueHolders in the domain. I personally favor Beans and assume that most accessor names do not expose the domain logic to be hidden by the obfuscator.

(c) 2010 JGoodies
 
jgoodies-binding-2.1.0/docs/guide/domainobjects.html0000644000175000017500000001350411374522114022422 0ustar twernertwerner JGoodies Binding :: Guide :: Domain Objects
:: JGOODIES Looks :: Professional Swing Look&Feels

:: Guide :: Domain Objects ::

Beans

The Binding is about synchronizing domain object properties with user interface components. We get values from the domain to put them into the UI component, and we set values in the domain object if the UI component changes.

The Java Bean standard describes a uniform approach to get and set properties, or in other words, how to read and write values from/to domain objects.

Observables (Bound Bean Properties)

In most cases we want to update the view if a domain object property changes. Therefore we must be able to observe changes. The Java Bean standard describes a mechanism to notify observers about changes in a single or multiple bean properties.

The so called bound Bean properties fire a PropertyChangeEvent if the property value changes. The Bean standard describes that a bound Bean must provide two methods to add and remove a PropertyChangeListeners. For convenience, the Binding includes the abstract class Model that provides everything necessary to register listeners and to fire the property change events.

ValueHolders

ValueHolders are an alternative to bound bean properties as described above. A ValueHolder is the minimal ValueModel implementation. It holds a single value that we can get and set, and it notifies listeners about value changes.

I recommend to favor bound bean properties over ValueHolders. Bean properties are much wider known in the Java world and work well with other libraries and programming styles. A noticeable difference between ValueHolders and properties is, that the ValueHolder is compile-time safe, where accessing a Bean property may fail at runtime. Anyway, I use ValueHolders only rarely and for internal observable values.

(c) 2010 JGoodies
 
jgoodies-binding-2.1.0/docs/guide/introduction.html0000644000175000017500000002502311374522114022321 0ustar twernertwerner JGoodies Binding :: Guide :: Introduction
:: JGOODIES Looks :: Professional Swing Look&Feels

:: Guide :: Introduction ::

The JGoodies Binding builds adapter chains between the domain layer and the presentation layer and updates both sides: views are updated if domain object properties have been changed, and domain properties are updated if the presentation has been changed.

Related Documentation

You can find a presentation about Binding techniques at the JGoodies Articles page.

Currently the primary documentation are the accompanying tutorial sources. These demonstrate typical uses of the Binding library. It is strongly recommended to browse and study all tutorial example classes.

Martin Fowler is in the process of adding further patterns to his catalog of "Patterns for Enterprise Application Architecture" [P of EAA]. These additions contain the Presentation Model pattern that can significantly improve the architecture and maintainability of your Swing applications.

An overview of the application architecture around this data binding is available at pp. 255 in this Application Guide. There's an introduction to ValueModels, and you may read the Comparison of our approach with MVP.

Domain Layer

The tutorial is built around the Album class. It provides 'aspects' that we want to present in a UI, and a UI may update the values of these aspects, or we build up a two-way connection between the UI and the aspects and each side can update the other. In other words, the model's aspects are bound to UI components.

The aspects can be read-write, read-only, or write-only; typically they are read-write. In addition such an aspect can be observable, i.e. other objects can observe changes of the aspect's value. There are different approaches to implement aspects.

In the Java world there's a well-received standard that many people work with: the Java Bean standard. Beans provide everything necessary for accessing and observing aspects - here they are called properties. And it's quite easy to follow the Java Bean naming conventions. Basically you provide a pair of getter and setter methods for each property. And typically you fire a PropertyChangeEvent in the setter to notify observers about property changes. The Binding works with other domain object styles, but many convenience classes focus on Beans. Also, if you use Beans, you need no references from your domain classes to the JGoodies Binding library. This makes it easier to change the binding style later.

In the tutorial the Album class extends Model and provides getters and setters for each property, for example #getTitle() and setTitle(String).

From Domain to ValueModel

The PresentationModel class vends adapters that convert the domain Bean properties into the ValueModel interface. For example to access an Album's title we write: new PresentationModel(anAlbum).getModel("title"). We can then read and write the title via the ValueModel methods #getValue/#setValue.

ValueModel

We now can read and write domain aspects using a uniform and very simple API (getValue/setValue) and can observe value changes. We can operate on these values to convert types, to buffer values, to cache values, etc. We can reuse a set of classes for each of these tasks. For example the BufferedValueModel delays value changes until you commit them. Or we can change the value source using a multiplexer, so that we can change the customer instance we're asking for the last name. Think of a JList of albums where you select different albums and a details view shows the title and artist of the current selection in a form with text field.

From ValueModel to Swing Model

To use a ValueModel with a Swing component, we've to convert from the #getValue/#setValue interface to the model interface as required by the component class. For example ValueModels that holds Strings can be converted to the Document interface that is the model for JTextComponent. Or a ValueModel that holds a Boolean can be converted to a ToggleButtonModel for use in a JCheckBox or JRadioButton. All these adapters can replace the Swing convenience model implementations. The Binding library provides prepared adapters to Swing components in package com.jgoodies.binding.adapter.

In some cases we need to set Swing component properties that have no underlying model. For example, the value property of the JFormattedTextField has no model that stores this value. The PropertyConnector class can connect two properties in two beans and synchronizes each property value with the other.

Pros and Cons

This binding approach can be used in many applications - I'd say in about 70 or 80% of all Swing applications. It can significantly save time to bind and buffer domain aspects in a Swing UI. However, the downside is that this approach increases the learning curve that is already quite high in Swing. And so, check carefully whether this binding is appropriate in your project.

Our binding leads to clean code and helps developers understand where to put what code: 1) implement domain aspects in domain Bean classes, 2) build panel classes that consist only of UI components and panel building code, 3) connect these two layers using a chain of adapters or using a middle code layer that holds these adapter so that other views can present the same data.

A true alternative to this binding is to copy values from the domain object to the convenience models of the Swing components and write the values back to the domain if you click on OK. The copying approach is easy to understand and works well if each aspect is presented by a single view. If multiple views present the same value, you may end up with cluttered code.

Anyway, even with the copying approach you can (and should) benefit from the Presentation Model pattern and a 3-tier architecture.

(c) 2010 JGoodies
 
jgoodies-binding-2.1.0/docs/guide/viewadapters.html0000644000175000017500000001553211374522114022302 0ustar twernertwerner JGoodies Binding :: Guide :: View Adapters
:: JGOODIES Looks :: Professional Swing Look&Feels

:: Guide :: View Adapters ::

Find below two lists that show the typical ways to bind single values and collections to Swing components. There are two convenience classes that assist in choosing the appropriate adapter and in binding a ValueModel to a Swing component:
  1. The BasicComponentFactory can create Swing components that are bound to a ValueModel; the factory chooses the appropriate adapter.
  2. The Bindings class is useful if you already have a component factory, or if you are using customized versions of the standard Swing components. the more basic Bindings class. It establishes the connection between a ValueModel and a given Swing component by setting the appropriate ValueModel adapter as the component's model.

Adapting Single Values

This table shows how to convert ValueModels to Swing component models. See also the ComponentsExample in the accompanying tutorial.

Component TypeAdapter / Connector
JCheckBoxToggleButtonAdapter
JCheckBoxMenuItemToggleButtonAdapter
JColorChooserColorSelectionAdapter
JFormattedTextFieldPropertyConnector
JLabelPropertyConnector
JPasswordFieldTextComponentConnector
JSliderBoundedRangeAdapter
JTextAreaTextComponentConnector
JTextFieldTextComponentConnector
JToggleButtonToggleButtonAdapter
JRadioButtonRadioButtonAdapter
JRadioButtonMenuItemRadioButtonAdapter

Adapting Collections

This table shows how to bind lists to JComboBox, JList and JTable. See also the MasterDetailsXXXExamples in the accompanying tutorial.

Component TypeAdapter / Connector
JComboBoxComboBoxAdapter on a ListModel or SelectionInList
JListSelectionInList or
ListModel
JTableAbstractTableAdapter wrapping a SelectionInList or
AbstractTableAdapter wrapping a ListModel or
Custom TableModel

(c) 2010 JGoodies
 
jgoodies-binding-2.1.0/docs/images/0000755000175000017500000000000011374522114017060 5ustar twernertwernerjgoodies-binding-2.1.0/docs/images/banner.gif0000644000175000017500000001007711374522114021021 0ustar twernertwernerGIF89a.:::fee~}}QQQ]]]uttDDDZYYrrrFFFjiizzz111JJJ$$$www...pppVVVxxxlllcbbONNɽѿooonnnCCConnrqqnmmGGGpooPOOzyy|{{///[[[EEEgggdccSSSkjj!,.~ōɼΛ~-шҌ}-vHX8n*'nÇN{*2j(Ŵ4vWUE\ɲƗ"aʜBa@vqƌ@D?C9'њ JR8|0ju֯?6*دEFS3l pƭtȇ=[P=RJj=ج+K+˘kjAf GSȑf.լ[}w\Rrq(_2С΍tUk>ݵfm[41v!c<{ )r돿z76 p;Bi֡ÃnVX~fam^oX0'/ ?|afjÎ<^|!~'S6&yK?;%[潷TXfeb8%>,h{p`g'ti䀎vRh ֬*H A쮛nk/kôκ 4p b4DA0! b;,j ,Zgz&4F^ٷ   X ,L0Qr8T] -GLaa2h@1X -(Œ "'``ID*IꜸT@@wo.p5N )&ΧKpBK8wd@$`5Z!( ޟ2 ^B}: YpKad-1g#4vfjQNzJ(w`Z]5O ^ aht{L_&} `8 Ԙqzjفcd"ϐ߱b LE*F:yX}_wXMa9K|,f1=5F a28@d\* yjLr .R3EPs4h~sBTش斿0OB;5vX!A hP#Y@@^,Ts;V 58Dcc<\Ey 7k,nEGhw%(@cZxz~6EdESEt0Eft@UPF! F,@M dO@JLy]tt2X.E@hKPGT@]@$VlMyFdOF]AXbHg@Xf٧wCC[~ tR,PQ)0;8=E{[=3LN$sqPD@cD %=vDTAB̓ZT01t|'tr,-KDߓTճ/@(e[H=heCs ,yPP7`MypnJ5[LvHt70 d2UV8&YVq,a07{pAV#) P6%s  (@6@pk`8(0 x`R&;($=([ |2.҃G$,(s VIBi89W$U42hXB{,PD,zCPbl` P?xrkHxhx4:pK0`+) 1mC/m@Ah:0h4tArY4Us5^S53mJ0XxAp0lRCH1H1G0esf`YI<#d wqՔF; B '-)"(9*.y/Bu"ʁ`2TAd/iYBAA9@E'IqS-q5dC7dK:4l2"&)"(aR,aK10wB*)y) ڕAÀR@JK~\Qr!!*ڢV2%=%)1 #ͥ#e"[1`"q:p1{p硾C!~9";Qљ`b'A?gbA;'"7!ǂ,6JSU*\$Ya7dѨ*?]3I1ѥn'g:;jJ Z5)Z:Z1Jc 3e|{/hBJSL%Z`0!8#.[A`!٪!"J L  ]ѫQ-: P{p⁰KM!8۱ $3a&K RGr0Ӯ2{8 ꐳ<۳ >B;D[FK;jgoodies-binding-2.1.0/docs/references.html0000644000175000017500000002245311374522114020630 0ustar twernertwerner JGoodies Binding :: References
:: JGOODIES :: Binding

:: References ::

Binding Presentations

Desktop Patterns & Data Binding motivates the separation of the presentation logic from the presentation, and the patterns supported by the JGoodies Binding. It also outlines how to achieve such a separation. And it includes comments and findings about automatic data binding systems. The Swing Data Binding presentation provides more details: the main Binding concepts, and how adapter chains can be used to synchronize domain object properties with Swing components. See the JGoodies Articles Page.

Binding User's Guide

Introduces the main concepts used in the Binding, explains how to convert domain object properties to a generic form, how to build adapter chains from ValueModels to Swing components, and how to address typical binding tasks.

Binding API Docs

Describe, specify and explain the Binding API. The class comments often motivate the need for a Binding class and contain basic examples how to use it.

Binding Tutorial

Source code examples that show typical uses and different aspects of the Binding in action. See the folder tutorial under this distribution's root directory. The tutorial is based on the examples in Martin Fowler's pattern catalog (see below).

Fowler's further Patterns of EAA

It is strongly recommended to study Martin Fowler's additions to his book "Patterns of Enterprise Application Architecture" (P of EAA). These patterns are very valuable for structuring and implementing a Swing application - for all binding styles. Organizing Presentation Logic motivates why you may separate the presentation logic from the domain and how to achieve this separation. The Presentation Model pattern is used again and again in the Binding tutorial.


JSR 295 - Beans Binding

This Java Specification Request is about keeping Java Beans properties in synch. It's scope is very similar to the JGoodies Binding. An implementation shall become part of Java 7; a prototype will likely show up as a public project at java.net. More...

Swing Labs Data Binding

Sun's Swing Labs project includes a data binding. It has been separated from the former JDNC project, and integrates with the Swing Labs SwingX project. More...

Oracle Application Development Framework (ADF)

Oracle's Application Development Framework (ADF) includes a data binding; the ADF ships with Oracle's JDeveloper Java IDE. This IDE supports the ADF binding and integrates the binding with a visual builder. More...
Oracle also has initiated the data binding JSR 227.

Spring Rich Client Project

Part of this larger desktop project is a data binding very similar to the JGoodies Binding that uses several of the JGoodies Binding adapters. The project integrates the binding with other mechanisms and aims to simplify the construction of rich Swing applications. More...

SWT Binding

Jayasoft's SWTBinding adds SWT/JFace support to the JGoodies Binding. It's an open source layer on top of the Binding that tries to get as close as possible to the underlying Binding API. More...

GlazedLists

A toolkit for list transformations that allows to sort and filter observable lists. More...

CUF (Client Utilities & Framework)

An application-level framework and collection of utilities. Contains a data binding much like the JGoodies Binding, a declarative state management, etc. More...

Jakarta Commons: BeanUtils

A set of Bean introspection utilities that offer low-level support for getting and setting property values in classes that follow the naming design patterns BeanUtils described in the Java Bean specification. More...

JXPath

An XPath interpreter for Java that allows to access general object data in Java Beans, Maps, DOM, and mixtures of these. Could be combined with the JGoodies Binding to implement a general-purpose data access ValueModel. More...

OGNL

An object graph navigation language that can be used to implement general-purpose data access ValueModels. More...
(c) 2010 JGoodies
 
jgoodies-binding-2.1.0/docs/style.css0000644000175000017500000000154311374522114017470 0ustar twernertwernerbody, p, td, li, h1, h2, h3 { font-family: Verdana, Arial, Helvetica, Geneva, sans-serif; } body, p, td, li, h2, h3 { font-size: 12px; } h1, h2, h3 { font-weight: bold; color:#323777; margin-bottom: 11px; } h1 { font-size:14px; } h2, h3 { margin-top: 17px; } .header { font-weight: bold; color:#323777; margin-bottom: 11px; } p { margin-top: 14px; } a { text-decoration: none; color:#323788; } a:hover { text-decoration: underline; } a.nav { text-decoration: none; color:#000000; } a.nav:hover { text-decoration: underline; } pre { font-family:Courier New, Courier, monospace; font-size:12px; } ul { list-style-type: square; margin-top: 2px; } td.header { color: #FFFFFF; background-color:#323777; } td.cell { background-color:#FFFFFF; }jgoodies-binding-2.1.0/README.html0000644000175000017500000001631611374522114016515 0ustar twernertwerner JGoodies Binding :: README
:: JGOODIES :: Binding

:: Readme :: Binding 2.1.0 ::

The JGoodies Binding synchronizes object properties with Swing components. And it helps you represent the state and behavior of a presentation independently of the GUI components used in the interface.

Since version 2.0 the Binding requires Java 5 or later. Binding versions for Java 1.4 are available in the JGoodies Archive.

Top Questions

:: What are the main benefits?
:: Can I use the Binding in a commercial product?
:: How to get started?
:: Where do I get support?
:: How to report a problem?

Distribution Contents

jgoodies-binding-2.1.0.jar   the library jar
 
docs/api   JavaDocs directory
 
lib   required libraries
 
src/core   Java sources for the library core
src/extras   Java sources for useful extensions
src/test   Java sources for unit tests
 
LICENSE.txt   the license agreement
README.html   this readme file
RELEASE-NOTES.txt   Release Notes
build.xml   ANT build file
default.properties   ANT default build properties

Acknowledgments

The Binding project has been financed by customers of the JGoodies Swing Suite. It is only with their money that I can provide, maintain and improve this library at no charge.

The Binding has been inspired by the VisualWorks ValueModel hierarchy, the PresentationModel pattern, and the Java Beans standard.

You can Help

If you save time and money using the JGoodies Binding, please help me finance my Java desktop activities by licensing the commercial JGoodies Swing Suite.
(c) 2010 JGoodies