001: /*
002: * MyGWT Widget Library
003: * Copyright(c) 2007, MyGWT.
004: * licensing@mygwt.net
005: *
006: * http://mygwt.net/license
007: */
008: package net.mygwt.ui.client.data;
009:
010: import java.util.ArrayList;
011: import java.util.HashMap;
012: import java.util.Iterator;
013: import java.util.List;
014: import java.util.Map;
015:
016: import com.google.gwt.user.client.rpc.IsSerializable;
017:
018: /**
019: * <code>Models</code> are generic data structures that notify listeners when
020: * changed. The structure allows a form of 'introspection' as all property names
021: * and values can be queried and retrieved at runtime.
022: *
023: * <p>
024: * All events fired by the model will bubble to all parents.
025: * </p>
026: *
027: * <p>
028: * Model objects implement <code>Serializable</code> and can therefore
029: * be used with GWT RPC. A model's children are not marked transient and will be
030: * passed in remote procedure calls.
031: * </p>
032: *
033: * <dl>
034: * <dt><b>Events:</b></dt>
035: *
036: * <dd><b>Model.Add</b> : (source, item)<br>
037: * <div>Fires after the button is selected.</div>
038: * <ul>
039: * <li>source : this</li>
040: * <li>item : add item</li>
041: * </ul>
042: * </dd>
043: *
044: * <dd><b>Model.Insert</b> : (source, item)<br>
045: * <div>Fires after the button is selected.</div>
046: * <ul>
047: * <li>source : this</li>
048: * <li>item : insert item</li>
049: * <li>index : insert index</li>
050: * </ul>
051: * </dd>
052: *
053: * <dd><b>Model.Update</b> : (source, item)<br>
054: * <div>Fires after the button is selected.</div>
055: * <ul>
056: * <li>source : this</li>
057: * <li>item : this</li>
058: * </ul>
059: * </dd>
060: * </dl>
061: *
062: * @see ChangeListener
063: * @see IsSerializable
064: */
065: public class Model implements IsSerializable {
066:
067: /**
068: * Fired when a child object is added to the model (value is 10).
069: */
070: public static final int Add = 10;
071:
072: /**
073: * Fired when a child object is inserted to the model (value is 20).
074: */
075: public static final int Insert = 20;
076:
077: /**
078: * Fired when a child object is removed from the model (value is 30).
079: */
080: public static final int Remove = 30;
081:
082: /**
083: * Fired when the model has beed updated (value is 40).
084: */
085: public static final int Update = 40;
086:
087: /**
088: * The model's parent.
089: */
090: protected Model parent;
091:
092: /**
093: * The model's children.
094: *
095: * @gwt.typeArgs <net.mygwt.ui.client.data.Model>
096: */
097: protected List children;
098:
099: /**
100: * The model's properties.
101: *
102: * @gwt.typeArgs <java.lang.String,com.google.gwt.user.client.rpc.IsSerializable>
103: */
104: protected Map properties;
105:
106: private transient List listeners;
107:
108: /**
109: * Creates a new model instance.
110: */
111: public Model() {
112: properties = new HashMap();
113: children = new ArrayList();
114: }
115:
116: /**
117: * Creates a new model instance with the specified properties.
118: *
119: * @param properties
120: */
121: public Model(Map properties) {
122: this .properties = properties;
123: children = new ArrayList();
124: }
125:
126: /**
127: * Adds a child to the model and fires an add event.
128: *
129: * @param child the child to be added
130: */
131: public void add(Model child) {
132: insert(child, getChildCount());
133: }
134:
135: /**
136: * Adds a listener to receive change events.
137: *
138: * @param listener the listener to be added
139: */
140: public void addChangeListener(ChangeListener listener) {
141: if (listeners == null) {
142: listeners = new ArrayList();
143: }
144: listeners.add(listener);
145: }
146:
147: /**
148: * Returns a properties value.
149: *
150: * @param name the property name
151: * @return the value
152: */
153: public Object get(String name) {
154: return properties.get(name);
155: }
156:
157: /**
158: * Returns a proprty value as a String by simplying calling toString on the
159: * value. Subclasses should override to provide more specific behavior.
160: *
161: * @param name the property name
162: * @return the String value
163: */
164: public String getAsString(String name) {
165: Object obj = get(name);
166: if (obj instanceof Boolean) {
167: if (((Boolean) obj).booleanValue()) {
168: return "Yes";
169: } else {
170: return "No";
171: }
172: }
173: return obj == null ? "" : obj.toString();
174: }
175:
176: /**
177: * Returns the child at the given index or <code>null</code> if the index is
178: * out of range.
179: *
180: * @param index the index to be retrieved
181: * @return the model at the index
182: */
183: public Model getChild(int index) {
184: if ((index < 0) || (index >= children.size()))
185: return null;
186: return (Model) children.get(index);
187: }
188:
189: /**
190: * Returns the number of children.
191: *
192: * @return the number of children
193: */
194: public int getChildCount() {
195: return children.size();
196: }
197:
198: /**
199: * Returns the model's children.
200: *
201: * @return the children
202: */
203: public List getChildren() {
204: return children;
205: }
206:
207: /**
208: * Returns the model's parent or <code>null</code> if no parent.
209: *
210: * @return the parent
211: */
212: public Model getParent() {
213: return parent;
214: }
215:
216: /**
217: * Returns an iterator for the model's property names.
218: *
219: * @return a iterator
220: */
221: public Iterator getPropertyNames() {
222: return properties.keySet().iterator();
223: }
224:
225: /**
226: * Inserts a child to the model and fires an insert event.
227: *
228: * @param child the child to be inserted
229: * @param index the location to insert the child
230: */
231: public void insert(Model child, int index) {
232: adopt(child);
233: children.add(index, child);
234: if (index == getChildCount() - 1) {
235: fireEvent(Add, child);
236: } else {
237: ChangeEvent evt = new ChangeEvent(Insert, this );
238: evt.item = child;
239: evt.index = index;
240: notify(evt);
241: }
242: }
243:
244: /**
245: * Removes the child at the given index.
246: *
247: * @param index the child index
248: */
249: public void remove(int index) {
250: if (index >= 0 && index < getChildCount()) {
251: remove(getChild(index));
252: }
253: }
254:
255: /**
256: * Removes the child from the model and fires a remove event.
257: *
258: * @param child the child to be removed
259: */
260: public void remove(Model child) {
261: child.parent = null;
262: children.remove(child);
263: fireEvent(Remove, child);
264: }
265:
266: /**
267: * Removes all the model's children.
268: */
269: public void removeAll() {
270: for (int i = children.size() - 1; i >= 0; i--) {
271: remove(getChild(i));
272: }
273: }
274:
275: /**
276: * Removes a previously added change listener.
277: *
278: * @param listener the listener to be removed
279: */
280: public void removeChangeListener(ChangeListener listener) {
281: if (listeners != null) {
282: listeners.remove(listener);
283: }
284: }
285:
286: /**
287: * Sets the property and fires an update event.
288: *
289: * @param name the property name
290: * @param value the property value
291: */
292: public void set(String name, Object value) {
293: properties.put(name, value);
294: fireEvent(Update, this );
295: }
296:
297: /**
298: * Sets the model's children. All existing children are first removed.
299: *
300: * @param children the children to be set
301: */
302: public void setChildren(List children) {
303: removeAll();
304: Model[] models = (Model[]) children.toArray(new Model[children
305: .size()]);
306: for (int i = 0; i < models.length; i++) {
307: add(models[i]);
308: }
309: }
310:
311: protected void fireEvent(int type) {
312: notify(new ChangeEvent(type, this ));
313: }
314:
315: protected void fireEvent(int type, Model item) {
316: notify(new ChangeEvent(type, this , item));
317: }
318:
319: protected void notify(ChangeEvent evt) {
320: if (listeners != null) {
321: for (int i = 0; i < listeners.size(); i++) {
322: ChangeListener listener = (ChangeListener) listeners
323: .get(i);
324: listener.modelChanged(evt);
325: }
326: }
327: if (parent != null) {
328: parent.notify(evt);
329: }
330: }
331:
332: private void adopt(Model child) {
333: if (child.parent != null && child.parent != this) {
334: child.parent.remove(child);
335: }
336: child.parent = this;
337: }
338:
339: }
|