001 /*
002 * Copyright 2002-2005 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.binding.value.support;
017
018 import java.beans.PropertyChangeEvent;
019 import java.beans.PropertyChangeListener;
020
021 import org.apache.commons.logging.Log;
022 import org.apache.commons.logging.LogFactory;
023 import org.springframework.binding.value.ValueChangeDetector;
024 import org.springframework.binding.value.ValueModel;
025 import org.springframework.richclient.application.ApplicationServicesLocator;
026
027 /**
028 * An abstract class that minimizes the effort required to implement
029 * the {@link ValueModel} interface. It provides convenience methods
030 * to convert boolean, double, float, int, and long to their
031 * corresponding Object values.
032 *
033 * <p>Subclasses must implement <code>getValue()</code> and
034 * <code>setValue(Object)</code> to get and set the observable value.
035 *
036 * @author Karsten Lentzsch
037 * @author Keith Donald
038 * @author Oliver Hutchison
039 */
040 public abstract class AbstractValueModel extends AbstractPropertyChangePublisher implements ValueModel {
041
042 protected final Log logger = LogFactory.getLog(getClass());
043
044 private final ThreadLocal listenerToSkipHolder = new ThreadLocal();
045
046 private ValueChangeDetector valueChangeDetector;
047
048 public final void setValueSilently(Object newValue, PropertyChangeListener listenerToSkip) {
049 // We need to keep track of listenerToSkip on a per thread basis as it's
050 // possible that this value model may be accessed from multiple threads.
051 final Object oldListenerToSkip = listenerToSkipHolder.get();
052 try {
053 listenerToSkipHolder.set(listenerToSkip);
054 setValue(newValue);
055 }
056 finally {
057 listenerToSkipHolder.set(oldListenerToSkip);
058 }
059 }
060
061 public final void addValueChangeListener(PropertyChangeListener listener) {
062 addPropertyChangeListener(VALUE_PROPERTY, listener);
063 }
064
065 public final void removeValueChangeListener(PropertyChangeListener listener) {
066 removePropertyChangeListener(VALUE_PROPERTY, listener);
067 }
068
069 /**
070 * This method can be called when it in necessary to send a
071 * PropertyChangeEvent to any registered PropertyChangeListeners
072 * even though the encapsulated value has not changed.
073 *
074 * FIXME: This needs a much better name!
075 */
076 protected void fireValueChangeWhenStillEqual() {
077 Object value = getValue();
078 fireValueChangeEvent(value, value);
079 }
080
081 /**
082 * Notifies all listeners that have registered interest for
083 * notification on this event type. The event instance
084 * is lazily created using the parameters passed into
085 * the fire method.
086 *
087 * @param oldValue the boolean value before the change
088 * @param newValue the boolean value after the change
089 */
090 protected final void fireValueChange(boolean oldValue, boolean newValue) {
091 fireValueChange(Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
092 }
093
094 /**
095 * Notifies all listeners that have registered interest for
096 * notification on this event type. The event instance
097 * is lazily created using the parameters passed into
098 * the fire method.
099 *
100 * @param oldValue the int value before the change
101 * @param newValue the int value after the change
102 */
103 protected final void fireValueChange(int oldValue, int newValue) {
104 fireValueChange(new Integer(oldValue), new Integer(newValue));
105 }
106
107 /**
108 * Notifies all listeners that have registered interest for
109 * notification on this event type. The event instance
110 * is lazily created using the parameters passed into
111 * the fire method.
112 *
113 * @param oldValue the long value before the change
114 * @param newValue the long value after the change
115 */
116 protected final void fireValueChange(long oldValue, long newValue) {
117 fireValueChange(new Long(oldValue), new Long(newValue));
118 }
119
120 /**
121 * Notifies all listeners that have registered interest for
122 * notification on this event type. The event instance
123 * is lazily created using the parameters passed into
124 * the fire method.
125 *
126 * @param oldValue the double value before the change
127 * @param newValue the double value after the change
128 */
129 protected final void fireValueChange(double oldValue, double newValue) {
130 fireValueChange(new Double(oldValue), new Double(newValue));
131 }
132
133 /**
134 * Notifies all listeners that have registered interest for
135 * notification on this event type. The event instance
136 * is lazily created using the parameters passed into
137 * the fire method.
138 *
139 * @param oldValue the float value before the change
140 * @param newValue the float value after the change
141 */
142 protected void fireValueChange(Object oldValue, Object newValue) {
143 if (hasValueChanged(oldValue, newValue)) {
144 fireValueChangeEvent(oldValue, newValue);
145 }
146 }
147
148 /**
149 * Delegates to configured <code>ValueChangeDetector</code>.
150 */
151 protected boolean hasValueChanged(Object oldValue, Object newValue) {
152 return getValueChangeDetector().hasValueChanged(oldValue, newValue);
153 }
154
155 /**
156 * Notifies all listeners that have registered interest for
157 * notification on this event type. This method does not check if there is any change
158 * between the old and new value unlike the various fireValueChanged() methods.
159 */
160 protected void fireValueChangeEvent(Object oldValue, Object newValue) {
161 if (logger.isDebugEnabled()) {
162 logger.debug("Firing value changed event. Old value='" + oldValue + "' new value='" + newValue + "'");
163 }
164 final PropertyChangeListener[] propertyChangeListeners = getPropertyChangeListeners(VALUE_PROPERTY);
165 if (propertyChangeListeners.length > 0) {
166 final Object listenerToSkip = listenerToSkipHolder.get();
167 final PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(this, VALUE_PROPERTY, oldValue,
168 newValue);
169 for (int i = 0; i < propertyChangeListeners.length; i++) {
170 PropertyChangeListener listener = propertyChangeListeners[i];
171 if (listener != listenerToSkip) {
172 listener.propertyChange(propertyChangeEvent);
173 }
174 }
175 }
176 }
177
178 /**
179 * Set the object that will be used to detect changes between two values.
180 * @param valueChangeDetector to use
181 */
182 public void setValueChangeDetector(ValueChangeDetector valueChangeDetector) {
183 this.valueChangeDetector = valueChangeDetector;
184 }
185
186 /**
187 * Get the installed value change detector. If none has been directly installed then
188 * get the one configured in the application context.
189 * @return value change detector to use
190 */
191 protected ValueChangeDetector getValueChangeDetector() {
192 if( valueChangeDetector == null ) {
193 valueChangeDetector = (ValueChangeDetector)ApplicationServicesLocator.services().getService(ValueChangeDetector.class);
194 }
195 return valueChangeDetector;
196 }
197
198 }