001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package nextapp.echo2.app.update;
031:
032: import java.io.Serializable;
033: import java.util.HashMap;
034: import java.util.HashSet;
035: import java.util.Map;
036: import java.util.Set;
037:
038: import nextapp.echo2.app.Component;
039:
040: /**
041: * A description of a server-side update to a single component, i.e.,
042: * an update which has occurred on the server and must be propagated
043: * to the client.
044: *
045: * Describes the addition and removal of children to the component.
046: * Describes modifications to properties of the component.
047: * Describes modifications to the <code>LayoutData</code> states of
048: * children of the component.
049: */
050: public class ServerComponentUpdate implements Serializable {
051:
052: private static final Component[] EMPTY_COMPONENT_ARRAY = new Component[0];
053: private static final String[] EMPTY_STRING_ARRAY = new String[0];
054:
055: /**
056: * The set of child <code>Component</code>s added to the <code>parent</code>.
057: */
058: private Set addedChildren;
059:
060: /**
061: * The parent component represented in this <code>ServerComponentUpdate</code>.
062: */
063: private Component parent;
064:
065: /**
066: * A mapping between property names of the <code>parent</code> component and
067: * <code>PropertyUpdate</code>s.
068: */
069: private Map propertyUpdates;
070:
071: /**
072: * The set of child <code>Component</code>s removed from the <code>parent</code>.
073: */
074: private Set removedChildren;
075:
076: /**
077: * The set of descendant <code>Component</code>s which are implicitly removed
078: * as they were children of removed children.
079: */
080: private Set removedDescendants;
081:
082: /**
083: * The set of child <code>Component</code>s whose <code>LayoutData</code>
084: * was updated.
085: */
086: private Set updatedLayoutDataChildren;
087:
088: /**
089: * Creates a new <code>ServerComponentUpdate</code> representing the given
090: * <code>parent</code> <code>Component</code>.
091: */
092: public ServerComponentUpdate(Component parent) {
093: this .parent = parent;
094: }
095:
096: /**
097: * Adds a description of an added child to the
098: * <code>ServerComponentUpdate</code>.
099: *
100: * @param child the child being added
101: */
102: public void addChild(Component child) {
103: if (addedChildren == null) {
104: addedChildren = new HashSet();
105: }
106: addedChildren.add(child);
107: }
108:
109: /**
110: * Cancels an update to a property. A cancellation of a property update
111: * is performed when the update manager discovers that the state of a
112: * property is already correct on the client.
113: *
114: * @param propertyName the property update to cancel
115: */
116: public void cancelUpdateProperty(String propertyName) {
117: if (propertyUpdates == null) {
118: return;
119: }
120: propertyUpdates.remove(propertyName);
121: if (propertyUpdates.size() == 0) {
122: propertyUpdates = null;
123: }
124: }
125:
126: /**
127: * Appends the removed child and descendant components of the given
128: * <code>ServerComponentUpdate</code> to this
129: * <code>ServerComponentUpdate</code>'s list of removed descendants.
130: * This method is invoked by the <code>ServerUpdateManager</code> when
131: * a component is removed that is an ancestor of a component that has
132: * been updated. In such a case, the descendant
133: * <code>ServerComponentUpdate</code> is destroyed,
134: * and thus its removed descendant components must be stored in the
135: * ancestor <code>ServerComponentUpdate</code>.
136: *
137: * @param update the <code>ServerComponentUpdate</code> whose removed
138: * descendants are to be appended
139: */
140: public void appendRemovedDescendants(ServerComponentUpdate update) {
141: // Append removed descendants.
142: if (update.removedDescendants != null) {
143: if (removedDescendants == null) {
144: removedDescendants = new HashSet();
145: }
146: removedDescendants.addAll(update.removedDescendants);
147: }
148: // Append removed children.
149: if (update.removedChildren != null) {
150: if (removedDescendants == null) {
151: removedDescendants = new HashSet();
152: }
153: removedDescendants.addAll(update.removedChildren);
154: }
155: }
156:
157: /**
158: * Returns the child components which have been added to the parent.
159: *
160: * @return the added child components
161: */
162: public Component[] getAddedChildren() {
163: if (addedChildren == null) {
164: return EMPTY_COMPONENT_ARRAY;
165: } else {
166: return (Component[]) addedChildren
167: .toArray(new Component[addedChildren.size()]);
168: }
169: }
170:
171: /**
172: * Returns the parent component being updated.
173: *
174: * @return the parent component
175: */
176: public Component getParent() {
177: return parent;
178: }
179:
180: /**
181: * Returns the child components which have been removed from the parent.
182: * These components may or may not have ever been rendered by the container,
183: * e.g., if a component was added and removed in a single synchronization it
184: * will show up as a removed component even though the container may have
185: * never rendered it.
186: *
187: * @return the removed child components
188: * @see #getRemovedDescendants()
189: */
190: public Component[] getRemovedChildren() {
191: if (removedChildren == null) {
192: return EMPTY_COMPONENT_ARRAY;
193: } else {
194: return (Component[]) removedChildren
195: .toArray(new Component[removedChildren.size()]);
196: }
197: }
198:
199: /**
200: * Returns all descendants of the child components which have been
201: * removed from the parent. This returned array DOES NOT contain the
202: * children which were directly removed from the parent component.
203: * These components may or may not have ever been rendered by the container,
204: * e.g., if a component was added and removed in a single synchronization it
205: * will show up as a removed descendant even though the container may have
206: * never rendered it.
207: *
208: * @return the removed descendant components
209: * @see #getRemovedChildren()
210: */
211: public Component[] getRemovedDescendants() {
212: if (removedDescendants == null) {
213: return EMPTY_COMPONENT_ARRAY;
214: } else {
215: return (Component[]) removedDescendants
216: .toArray(new Component[removedDescendants.size()]);
217: }
218: }
219:
220: /**
221: * Returns the child components whose <code>LayoutData</code> properties
222: * have been updated.
223: *
224: * @return the changed child components
225: */
226: public Component[] getUpdatedLayoutDataChildren() {
227: if (updatedLayoutDataChildren == null) {
228: return EMPTY_COMPONENT_ARRAY;
229: } else {
230: return (Component[]) updatedLayoutDataChildren
231: .toArray(new Component[updatedLayoutDataChildren
232: .size()]);
233: }
234: }
235:
236: /**
237: * Returns a <code>PropertyUpdate</code> describing an update to the
238: * property with the given <code>name</code>.
239: *
240: * @param name the name of the property being updated
241: * @return the <code>PropertyUpdate</code>, or null if none exists
242: * @see #getUpdatedPropertyNames()
243: */
244: public PropertyUpdate getUpdatedProperty(String name) {
245: return propertyUpdates == null ? null
246: : (PropertyUpdate) propertyUpdates.get(name);
247: }
248:
249: /**
250: * Returns the names of all properties being updated in this update.
251: *
252: * @return the names of all updated properties
253: * @see #getUpdatedPropertyNames()
254: */
255: public String[] getUpdatedPropertyNames() {
256: if (propertyUpdates == null) {
257: return EMPTY_STRING_ARRAY;
258: } else {
259: return (String[]) propertyUpdates.keySet().toArray(
260: new String[propertyUpdates.size()]);
261: }
262: }
263:
264: /**
265: * Determines if the specified component has been added
266: * as a child in this update.
267: *
268: * @param component the component to test
269: * @return true if the component was added
270: */
271: public boolean hasAddedChild(Component component) {
272: return addedChildren != null
273: && addedChildren.contains(component);
274: }
275:
276: /**
277: * Determines if the update is adding any children to the parent component.
278: *
279: * @return true if children are being added
280: */
281: public boolean hasAddedChildren() {
282: return addedChildren != null;
283: }
284:
285: /**
286: * Determines if the specified child was removed from the parent component.
287: *
288: * @param component the potentially removed child
289: * @return true if the child was removed
290: */
291: public boolean hasRemovedChild(Component component) {
292: return removedChildren != null
293: && removedChildren.contains(component);
294: }
295:
296: /**
297: * Determines if the update is removing children from the parent
298: * component.
299: *
300: * @return true if children are being removed
301: */
302: public boolean hasRemovedChildren() {
303: return removedChildren != null;
304: }
305:
306: /**
307: * Determines if the update is removing children from the parent that
308: * have descendants.
309: * Having removed descendants implies having removed children.
310: * If none of the children being removed have children, this method
311: * will return false.
312: *
313: * @return true if descendants are being removed
314: */
315: public boolean hasRemovedDescendants() {
316: return removedDescendants != null;
317: }
318:
319: /**
320: * Determines if the update has child components whose
321: * <code>LayoutData</code> states have changed.
322: *
323: * @return true if <code>LayoutData</code> properties are being updated
324: */
325: public boolean hasUpdatedLayoutDataChildren() {
326: return updatedLayoutDataChildren != null;
327: }
328:
329: /**
330: * Determines if the update is updating properties of the parent component.
331: *
332: * @return true if properties are being updated
333: */
334: public boolean hasUpdatedProperties() {
335: return propertyUpdates != null;
336: }
337:
338: /**
339: * Adds a description of a removed child to the
340: * <code>ServerComponentUpdate</code>.
341: *
342: * @param child the child being removed
343: */
344: public void removeChild(Component child) {
345: if (addedChildren != null && addedChildren.contains(child)) {
346: // Remove child from add list if found.
347: addedChildren.remove(child);
348: }
349: if (updatedLayoutDataChildren != null
350: && updatedLayoutDataChildren.contains(child)) {
351: // Remove child from updated layout data list if found.
352: updatedLayoutDataChildren.remove(child);
353: }
354: if (removedChildren == null) {
355: removedChildren = new HashSet();
356: }
357: removedChildren.add(child);
358:
359: Component[] descendants = child.getComponents();
360: for (int i = 0; i < descendants.length; ++i) {
361: removeDescendant(descendants[i]);
362: }
363: }
364:
365: /**
366: * Recursive method to add descriptions of descendants which were
367: * removed.
368: *
369: * @param descendant the removed descendant
370: */
371: public void removeDescendant(Component descendant) {
372: if (removedDescendants == null) {
373: removedDescendants = new HashSet();
374: }
375: removedDescendants.add(descendant);
376: Component[] descendants = descendant.getComponents();
377: for (int i = 0; i < descendants.length; ++i) {
378: removeDescendant(descendants[i]);
379: }
380: }
381:
382: /**
383: * Display debug representation.
384: *
385: * @see java.lang.Object#toString()
386: */
387: public String toString() {
388: StringBuffer out = new StringBuffer();
389: out.append(ServerComponentUpdate.class.getName() + "\n");
390: out.append("- Parent: " + getParent() + "\n");
391: out.append("- Adds: " + addedChildren + "\n");
392: out.append("- Removes: " + removedChildren + "\n");
393: out.append("- DescendantRemoves: " + removedDescendants + "\n");
394: out.append("- ChildLayoutDataUpdates: "
395: + updatedLayoutDataChildren + "\n");
396: out.append("- PropertyUpdates: " + propertyUpdates + "\n");
397: return out.toString();
398: }
399:
400: /**
401: * Adds a description of an update to a child component's
402: * <code>LayoutData</code> information to the
403: * <code>ServerComponentUpdate</code>.
404: *
405: * @param child the updated child
406: */
407: public void updateLayoutData(Component child) {
408: if (updatedLayoutDataChildren == null) {
409: updatedLayoutDataChildren = new HashSet();
410: }
411: updatedLayoutDataChildren.add(child);
412: }
413:
414: /**
415: * Adds a description of an update to a property of the parent component
416: * to the <code>ServerComponentUpdate</code>.
417: *
418: * @param propertyName the name of the property
419: * @param oldValue the previous value of the property
420: * @param newValue the current value of the property
421: */
422: public void updateProperty(String propertyName, Object oldValue,
423: Object newValue) {
424: if (propertyUpdates == null) {
425: propertyUpdates = new HashMap();
426: }
427: PropertyUpdate propertyUpdate = new PropertyUpdate(oldValue,
428: newValue);
429: propertyUpdates.put(propertyName, propertyUpdate);
430: }
431: }
|