001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.adapter;
032:
033: import java.util.Calendar;
034: import java.util.Date;
035:
036: import javax.swing.SpinnerDateModel;
037: import javax.swing.SpinnerModel;
038: import javax.swing.SpinnerNumberModel;
039:
040: import com.jgoodies.binding.value.ValueModel;
041:
042: /**
043: * A factory that vends {@link SpinnerModel} implementations that are bound to
044: * a ValueModel. Can be used to bind a ValueModel to instances of JSpinner.<p>
045: *
046: * To keep the ValueModel and SpinnerModel synchronized, this class listens
047: * to changes in both sides and updates the other silently, i.e. without
048: * firing a duplicate change event.<p>
049: *
050: * <strong>Constraints:</strong>
051: * The ValueModel's type must be compatible with the type required by the
052: * referenced SpinnerModel. For example a {@link SpinnerNumberModel} requires
053: * <code>Number</code> values.
054: *
055: * <strong>Example:</strong><pre>
056: * // General Connection
057: * ValueModel levelModel = new PropertyAdapter(settings, "level", true);
058: * SpinnerModel spinnerModel = new SpinnerNumberModel(9, 5, 10, 1);
059: * Object defaultValue = new Integer(9);
060: * SpinnerAdapterFactory.connect(spinnerModel, levelModel, defaultValue);
061: * JSpinner levelSpinner = new JSpinner(spinnerModel);
062: *
063: * // Short Form
064: * ValueModel levelModel = new PropertyAdapter(settings, "level", true);
065: * SpinnerNumberModel spinnerModel =
066: * SpinnerAdapterFactory.createNumberAdapter(levelModel, 5, 10, 1);
067: * JSpinner levelSpinner = new JSpinner(spinnerModel);
068: * </pre>
069: *
070: * @author Karsten Lentzsch
071: * @version $Revision: 1.10 $
072: *
073: * @see ValueModel
074: * @see SpinnerModel
075: * @see javax.swing.JSpinner
076: *
077: * @since 1.1
078: */
079: public final class SpinnerAdapterFactory {
080:
081: private SpinnerAdapterFactory() {
082: // Override default constructor; prevents instantiation.
083: }
084:
085: // Factory Methods ********************************************************
086:
087: /**
088: * Creates and returns a <code>SpinnerDateModel</code> bound to the given
089: * <code>valueModel</code>. The <code>calendarField</code>
090: * is equal to <code>Calendar.DAY_OF_MONTH</code>; there are no
091: * <code>start</code>/<code>end</code> limits.
092: *
093: * @param valueModel a <code>Date</code> typed model that holds the spinner value
094: * @param defaultDate the date used if the valueModel's value is <code>null</code>
095: * @return a <code>SpinnerDateModel</code> bound to the given
096: * <code>valueModel</code> without start and end limits using
097: * <code>Calendar.DAY_OF_MONTH</code> as calendar field
098: *
099: * @throws NullPointerException if the valueModel or defaultDate is <code>null</code>
100: */
101: public static SpinnerDateModel createDateAdapter(
102: ValueModel valueModel, Date defaultDate) {
103: return createDateAdapter(valueModel, defaultDate, null, null,
104: Calendar.DAY_OF_MONTH);
105: }
106:
107: /**
108: * Creates and returns a <code>SpinnerDateModel</code> that represents a sequence
109: * of dates and is bound to the given <code>valueModel</code>.
110: * The dates are between <code>start</code> and <code>end</code>. The
111: * <code>nextValue</code> and <code>previousValue</code> methods
112: * compute elements of the sequence by advancing or reversing
113: * the current date <code>value</code> by the
114: * <code>calendarField</code> time unit. For a precise description
115: * of what it means to increment or decrement a <code>Calendar</code>
116: * <code>field</code>, see the <code>add</code> method in
117: * <code>java.util.Calendar</code>.<p>
118: *
119: * The <code>start</code> and <code>end</code> parameters can be
120: * <code>null</code> to indicate that the range doesn't have an
121: * upper or lower bound. If <code>value</code> or
122: * <code>calendarField</code> is <code>null</code>, or if both
123: * <code>start</code> and <code>end</code> are specified and
124: * <code>minimum > maximum</code> then an
125: * <code>IllegalArgumentException</code> is thrown.
126: * Similarly if <code>(minimum <= value <= maximum)</code> is false,
127: * an IllegalArgumentException is thrown.<p>
128: *
129: * <strong>This method has not been tested.</strong>
130: *
131: * @param valueModel a <code>Date</code> typed model that holds the spinner value
132: * @param defaultDate the date used if the valueModel's value is <code>null</code>
133: * @param start the first date in the sequence or <code>null</code>
134: * @param end the last date in the sequence or <code>null</code>
135: * @param calendarField one of
136: * <ul>
137: * <li><code>Calendar.ERA</code>
138: * <li><code>Calendar.YEAR</code>
139: * <li><code>Calendar.MONTH</code>
140: * <li><code>Calendar.WEEK_OF_YEAR</code>
141: * <li><code>Calendar.WEEK_OF_MONTH</code>
142: * <li><code>Calendar.DAY_OF_MONTH</code>
143: * <li><code>Calendar.DAY_OF_YEAR</code>
144: * <li><code>Calendar.DAY_OF_WEEK</code>
145: * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code>
146: * <li><code>Calendar.AM_PM</code>
147: * <li><code>Calendar.HOUR</code>
148: * <li><code>Calendar.HOUR_OF_DAY</code>
149: * <li><code>Calendar.MINUTE</code>
150: * <li><code>Calendar.SECOND</code>
151: * <li><code>Calendar.MILLISECOND</code>
152: * </ul>
153: * @return a <code>SpinnerDateModel</code> bound to the given
154: * <code>valueModel</code> using the specified start and end dates
155: * and calendar field.
156: *
157: * @throws NullPointerException if the valueModel or defaultDate is <code>null</code>
158: * @throws IllegalArgumentException if <code>calendarField</code> isn't valid,
159: * or if the following expression is
160: * false: <code>(start <= value <= end)</code>.
161: *
162: * @see java.util.Calendar
163: * @see Date
164: */
165: public static SpinnerDateModel createDateAdapter(
166: ValueModel valueModel, Date defaultDate,
167: Comparable<Date> start, Comparable<Date> end,
168: int calendarField) {
169: if (valueModel == null)
170: throw new NullPointerException(
171: "The valueModel must not be null.");
172: if (defaultDate == null)
173: throw new NullPointerException(
174: "The default date must not be null.");
175:
176: Date valueModelDate = (Date) valueModel.getValue();
177: Date initialDate = valueModelDate != null ? valueModelDate
178: : defaultDate;
179: SpinnerDateModel spinnerModel = new SpinnerDateModel(
180: initialDate, start, end, calendarField);
181: connect(spinnerModel, valueModel, defaultDate);
182: return spinnerModel;
183: }
184:
185: /**
186: * Creates and returns a {@link SpinnerNumberModel} that is connected to
187: * the given {@link ValueModel} and that honors the specified minimum,
188: * maximum and step values.
189: *
190: * @param valueModel an <code>Integer</code> typed model that holds the spinner value
191: * @param defaultValue the number used if the valueModel's value is <code>null</code>
192: * @param minValue the lower bound of the spinner number
193: * @param maxValue the upper bound of the spinner number
194: * @param stepSize used to increment and decrement the current value
195: * @return a <code>SpinnerNumberModel</code> that is connected to the given
196: * <code>ValueModel</code>
197: *
198: * @throws NullPointerException if the valueModel is <code>null</code>
199: */
200: public static SpinnerNumberModel createNumberAdapter(
201: ValueModel valueModel, int defaultValue, int minValue,
202: int maxValue, int stepSize) {
203: return createNumberAdapter(valueModel, Integer
204: .valueOf(defaultValue), Integer.valueOf(minValue),
205: Integer.valueOf(maxValue), Integer.valueOf(stepSize));
206: }
207:
208: /**
209: * Creates and returns a {@link SpinnerNumberModel} that is connected to
210: * the given {@link ValueModel} and that honors the specified minimum,
211: * maximum and step values.
212: *
213: * @param valueModel a <code>Number</code> typed model that holds the spinner value
214: * @param defaultValue the number used if the valueModel's value is <code>null</code>
215: * @param minValue the lower bound of the spinner number
216: * @param maxValue the upper bound of the spinner number
217: * @param stepSize used to increment and decrement the current value
218: * @return a <code>SpinnerNumberModel</code> that is connected to the given
219: * <code>ValueModel</code>
220: *
221: * @throws NullPointerException if the valueModel or defaultValue is <code>null</code>
222: */
223: public static SpinnerNumberModel createNumberAdapter(
224: ValueModel valueModel, Number defaultValue,
225: Comparable<? extends Number> minValue,
226: Comparable<? extends Number> maxValue, Number stepSize) {
227: Number valueModelNumber = (Number) valueModel.getValue();
228: Number initialValue = valueModelNumber != null ? valueModelNumber
229: : defaultValue;
230: SpinnerNumberModel spinnerModel = new SpinnerNumberModel(
231: initialValue, minValue, maxValue, stepSize);
232: connect(spinnerModel, valueModel, defaultValue);
233: return spinnerModel;
234: }
235:
236: // Connecting a ValueModel with a General SpinnerModel*********************
237:
238: /**
239: * Connects the given ValueModel and SpinnerModel
240: * by synchronizing their values.
241: *
242: * @param spinnerModel the underlying SpinnerModel implementation
243: * @param valueModel provides a value
244: * @param defaultValue the value used if the valueModel's value is <code>null</code>
245: * @throws NullPointerException
246: * if the spinnerModel, valueModel or defaultValue is <code>null</code>
247: */
248: public static void connect(SpinnerModel spinnerModel,
249: ValueModel valueModel, Object defaultValue) {
250: new SpinnerToValueModelConnector(spinnerModel, valueModel,
251: defaultValue);
252: }
253: }
|