1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.trinidad.change;
20
21 import javax.faces.component.NamingContainer;
22 import javax.faces.component.UIComponent;
23 import javax.faces.context.FacesContext;
24
25 import org.apache.myfaces.trinidad.component.UIXComponent;
26 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
27 import org.apache.myfaces.trinidad.util.ComponentUtils;
28
29 import org.w3c.dom.Node;
30
31
32 /**
33 * Change specialization for moving a child from one container to another.
34 * MoveChildComponent should be registered on a parent component that is
35 * common to the child being moved and the container component at destination.
36 * In other words, while calling addComponentChange() or addDocumentChange()
37 * methods on the ChangeManager to add a MoveChildComponentChange, the common
38 * parent component instance must be passed as an argument. The add() utility
39 * method in this class can be alternatively used to conveniently register the
40 * change against the common parent. While applying this change, if a child with
41 * the same identifier as the movable child were to be already present in the
42 * destination container, it will be considered as a duplicate child, will be
43 * removed and movable child will be added.
44 * @see #add(FacesContext, ChangeManager)
45 * @see ChangeManager#addComponentChange(FacesContext, UIComponent, ComponentChange)
46 * @see ChangeManager#addDocumentChange(FacesContext, UIComponent, DocumentChange)
47 */
48 public final class MoveChildComponentChange
49 extends ComponentChange
50 implements DocumentChange
51 {
52 /**
53 * Constructs a MoveChildComponentChange. The child will be appended to the
54 * list of children of the destinationContainer.
55 * @param movableChild The child component to be moved.
56 * @param destinationContainer The destination component into which the child
57 * component is to be moved.
58 * @throws IllegalArgumentException If movableChild or destinationContainer
59 * is null
60 */
61 public MoveChildComponentChange(
62 UIComponent movableChild,
63 UIComponent destinationContainer)
64 {
65 this(movableChild, destinationContainer, null);
66 }
67
68 /**
69 * Constructs a MoveChildComponentChange. The child will be inserted to the
70 * list of children of the destinationContainer, before the supplied
71 * insertBeforecomponent. If the supplied insertBeforeComponent is null, the
72 * child will be appended to the list of children of the destinationContainer.
73 * If the insertBeforeComponent is non-null, and if it were not to be found
74 * while applying this change, the movableChild will not be moved.
75 * @param movableChild The child component to be moved.
76 * @param destinationContainer The destination component into which the child
77 * component is to be moved.
78 * @param insertBeforeComponent The component before which the moved child is
79 * to be inserted. This can be null, in which case the movableChild is
80 * appended.
81 * @throws IllegalArgumentException If movableChild or destinationContainer
82 * is null, or if a parent component common to movableChild and
83 * destinationContainer could not be found.
84 */
85 public MoveChildComponentChange(
86 UIComponent movableChild,
87 UIComponent destinationContainer,
88 UIComponent insertBeforeComponent)
89 {
90 if (movableChild == null)
91 throw new IllegalArgumentException(
92 _LOG.getMessage("MOVABLE_CHILD_REQUIRED"));
93
94 if (destinationContainer == null)
95 throw new IllegalArgumentException(
96 _LOG.getMessage("DESTINATION_CONTAINER_REQUIRED"));
97
98
99 _commonParent =
100 _getClosestCommonParentUIXComponent(movableChild, destinationContainer);
101
102 if (_commonParent == null)
103 throw new IllegalArgumentException(
104 _LOG.getMessage("COMMON_PARENT_NOT_FOUND"));
105
106
107 _movableChildScopedId =
108 ComponentUtils.getScopedIdForComponent(movableChild, _commonParent);
109 _sourceParentScopedId =
110 ComponentUtils.getScopedIdForComponent(movableChild.getParent(),
111 _commonParent);
112 _destinationContainerScopedId =
113 ComponentUtils.getScopedIdForComponent(destinationContainer, _commonParent);
114
115 _commonParentScopedId =
116 ComponentUtils.getScopedIdForComponent(_commonParent, null);
117
118 if (_movableChildScopedId == null ||
119 _sourceParentScopedId == null ||
120 _destinationContainerScopedId == null ||
121 _commonParentScopedId == null)
122 throw new IllegalArgumentException(
123 _LOG.getMessage("MOVE_PARTICIPANTS_WITHOUT_ID"));
124
125
126
127 String commonParentPrefix = _getScopedIdPrefix(_commonParent, _commonParentScopedId);
128
129 _sourceAbsoluteScopedId = (commonParentPrefix != null)
130 ? new StringBuilder(commonParentPrefix).
131 append(NamingContainer.SEPARATOR_CHAR).
132 append(_movableChildScopedId).toString()
133 : _movableChildScopedId;
134
135
136 String destinationContainerPrefix = _getScopedIdPrefix(destinationContainer,
137 _destinationContainerScopedId);
138
139 StringBuilder destinationScopedIdBuilder = new StringBuilder();
140
141 if (commonParentPrefix != null)
142 {
143 destinationScopedIdBuilder.append(commonParentPrefix).append(NamingContainer.SEPARATOR_CHAR);
144 }
145
146 if (destinationContainerPrefix != null)
147 {
148 destinationScopedIdBuilder.append(destinationContainerPrefix).append(NamingContainer.SEPARATOR_CHAR);
149 }
150
151 _destinationAbsoluteScopedId = destinationScopedIdBuilder.append(movableChild.getId()).toString();
152
153
154 _insertBeforeId = (insertBeforeComponent == null) ?
155 null:insertBeforeComponent.getId();
156 }
157
158 private String _getScopedIdPrefix(UIComponent component, String scopedId)
159 {
160 if (component instanceof NamingContainer)
161 return scopedId;
162 else
163 {
164
165 int separatorIndex = scopedId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
166
167 if (separatorIndex >= 0)
168 return scopedId.substring(0, separatorIndex);
169 else
170 {
171
172 return null;
173 }
174 }
175 }
176
177 /**
178 * Convenience method to add this MoveChildComponentChange to the supplied
179 * ChangeManager. The change will be registered against a parent component
180 * that is common to the child being moved and the container component at
181 * destination.
182 * @param facesContext The FacesContext instance for the current request
183 * @param changeManager The ChangeManager instance on which this
184 * MoveChildComponentChange is to be added.
185 * @return The common parent component against which this
186 * MoveChildComponentChange was registered.
187 */
188 public UIComponent add(
189 FacesContext facesContext,
190 ChangeManager changeManager)
191 {
192 UIComponent commonParent = _commonParent;
193
194 if (commonParent == null)
195 commonParent =
196 facesContext.getViewRoot().findComponent(_commonParentScopedId);
197 if (commonParent == null)
198 {
199 _LOG.warning("COMMON_PARENT_NOT_FOUND", _commonParentScopedId);
200 return null;
201 }
202
203
204 changeManager.addComponentChange(facesContext, commonParent, this);
205
206
207 _commonParent = null;
208
209 return commonParent;
210 }
211
212 /**
213 * Apply this change to the specified component.
214 * @param changeTargetComponent The component that is a common parent to the
215 * movable child and the destination container.
216 * @throws IllegalArgumentException If the supplied changeTargetComponent
217 * is null.
218 */
219 @Override
220 public void changeComponent(UIComponent changeTargetComponent)
221 {
222 if (changeTargetComponent == null)
223 throw new IllegalArgumentException(
224 _LOG.getMessage("COMPONENT_REQUIRED"));
225
226
227 UIComponent destinationContainer =
228 changeTargetComponent.findComponent(_destinationContainerScopedId);
229 if(destinationContainer == null)
230 {
231 _LOG.warning("DESTINATION_CONTAINER_NOT_FOUND",
232 _destinationContainerScopedId);
233 return;
234 }
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 UIComponent sourceParent =
250 changeTargetComponent.findComponent(_sourceParentScopedId);
251
252 UIComponent foundChild =
253 changeTargetComponent.findComponent(_movableChildScopedId);
254
255 UIComponent movableChild = foundChild;
256 int movableChildIndex = 0;
257 while (foundChild != null)
258 {
259
260 if (foundChild.getParent().equals(sourceParent))
261 {
262 movableChild = foundChild;
263 movableChildIndex = sourceParent.getChildren().indexOf(movableChild);
264 }
265
266
267
268
269
270 foundChild.getParent().getChildren().remove(foundChild);
271
272
273 foundChild = changeTargetComponent.findComponent(_movableChildScopedId);
274 }
275
276 if(movableChild == null)
277 {
278 _LOG.warning("MOVABLE_CHILD_NOT_FOUND", _movableChildScopedId);
279 return;
280 }
281
282
283 sourceParent.getChildren().add(movableChildIndex, movableChild);
284
285
286
287
288
289 String movableChildId = movableChild.getId();
290 int indexOfChildWithSameIdAtDestination = 0;
291 UIComponent childWithSameIdAtDestination = null;
292 for (UIComponent childComponent:destinationContainer.getChildren())
293 {
294 if (movableChildId.equals(childComponent.getId()))
295 {
296 indexOfChildWithSameIdAtDestination =
297 destinationContainer.getChildren().indexOf(childComponent);
298 childWithSameIdAtDestination = childComponent;
299 destinationContainer.getChildren().remove(childComponent);
300 }
301 }
302
303
304
305 int insertIndex = -1;
306 if (_insertBeforeId != null)
307 {
308 for (UIComponent childComponent:destinationContainer.getChildren())
309 {
310 if (_insertBeforeId.equals(childComponent.getId()))
311 {
312 insertIndex =
313 destinationContainer.getChildren().indexOf(childComponent);
314 break;
315 }
316 }
317
318
319
320
321 if (insertIndex == -1)
322 {
323 if (childWithSameIdAtDestination != null)
324 destinationContainer.getChildren().add(
325 indexOfChildWithSameIdAtDestination,
326 childWithSameIdAtDestination);
327
328 _LOG.warning("INSERT_BEFORE_NOT_FOUND", _insertBeforeId);
329 return;
330 }
331 }
332
333
334 if (insertIndex == -1)
335 destinationContainer.getChildren().add(movableChild);
336 else
337 destinationContainer.getChildren().add(insertIndex, movableChild);
338 }
339
340 /**
341 * Given the DOM Node representing a Component, apply any necessary
342 * DOM changes. The node passed will be the Node that is a common parent for
343 * the movable child and the destination container.
344 * There is a limitation with the document change, that the movable child
345 * Node, destination container Node, and the common parent Node have to belong
346 * to the same document.
347 * @param changeTargetNode DOM Node that is a common parent for the movable
348 * child and the destination container.
349 * @throws IllegalArgumentException If changeTargeNode were to be null.
350 */
351 public void changeDocument(Node changeTargetNode)
352 {
353 if (changeTargetNode == null)
354 throw new IllegalArgumentException(_LOG.getMessage("NO_NODE_SPECIFIED"));
355
356
357
358 Node movableChildNode =
359 ChangeUtils.__findNodeByScopedId(changeTargetNode,
360 _movableChildScopedId,
361 Integer.MAX_VALUE);
362
363 if(movableChildNode == null)
364 {
365 _LOG.warning("MOVABLE_CHILD_NOT_FOUND", _movableChildScopedId);
366 return;
367 }
368
369
370 Node destinationContainerNode =
371 ChangeUtils.__findNodeByScopedId(changeTargetNode,
372 _destinationContainerScopedId,
373 Integer.MAX_VALUE);
374
375
376 if(destinationContainerNode == null)
377 {
378 _LOG.warning("DESTINATION_CONTAINER_NOT_FOUND",
379 _destinationContainerScopedId);
380 return;
381 }
382
383
384 Node insertBeforeNode = (_insertBeforeId == null) ?
385 null:ChangeUtils.__findNodeByScopedId(destinationContainerNode,
386 _insertBeforeId,
387 1);
388
389
390 if(_insertBeforeId != null && insertBeforeNode == null)
391 {
392 _LOG.warning("INSERT_BEFORE_NOT_FOUND", _insertBeforeId);
393 return;
394 }
395
396
397 destinationContainerNode.insertBefore(movableChildNode, insertBeforeNode);
398 }
399
400 /**
401 * Returns true if adding the DocumentChange should force the JSP Document
402 * to reload
403 * @return true Since moving of components should force the document to reload
404 */
405 public boolean getForcesDocumentReload()
406 {
407 return true;
408 }
409
410 /**
411 * Returns the first UIXComponent common parent of two components in a
412 * subtree.
413 * @param firstComponent The first UIComponent instance
414 * @param secondComponent The second UIComponent instance
415 * @return UIComponent The closest common parent of the two supplied
416 * components.
417 */
418 private static UIComponent _getClosestCommonParentUIXComponent(
419 UIComponent firstComponent,
420 UIComponent secondComponent)
421 {
422 if (firstComponent == null || secondComponent == null)
423 return null;
424
425
426 int firstDepth = _computeDepth(firstComponent);
427 int secondDepth = _computeDepth(secondComponent);
428
429
430
431 if (secondDepth > firstDepth)
432 {
433 secondComponent = _getAncestor(secondComponent, secondDepth - firstDepth);
434 }
435 else if(secondDepth < firstDepth)
436 {
437 firstComponent = _getAncestor(firstComponent, firstDepth - secondDepth);
438 }
439
440
441 while (firstComponent != null && (firstComponent != secondComponent))
442 {
443 firstComponent = firstComponent.getParent();
444 secondComponent = secondComponent.getParent();
445 }
446
447
448
449 UIComponent sharedRoot = firstComponent;
450
451 while ((sharedRoot != null) && !(sharedRoot instanceof UIXComponent))
452 sharedRoot = sharedRoot.getParent();
453
454 return sharedRoot;
455 }
456
457 /**
458 * Returns the absolute scopedId of the source component
459 */
460 public String getSourceScopedId()
461 {
462 return _sourceAbsoluteScopedId;
463 }
464
465
466 /**
467 * Returns the absolute scopedId of the source component at its destination
468 */
469 public String getDestinationScopedId()
470 {
471 return _destinationAbsoluteScopedId;
472 }
473
474 /**
475 * Returns the depth of a UIComponent in the tree.
476 * @param comp the UIComponent whose depth has to be calculated
477 * @return the depth of the passed in UIComponent
478 */
479 private static int _computeDepth(UIComponent comp)
480 {
481 int i = 0;
482 while((comp = comp.getParent()) != null)
483 {
484 i++;
485 }
486 return i;
487 }
488
489 /**
490 * Returns the nth ancestor of the passed in component.
491 * @param component The UIComponent whose nth ancestor has to be found
492 * @param level Indicates how many levels to go up from the component
493 * @return The nth ancestor of the component
494 */
495 private static UIComponent _getAncestor(UIComponent component, int level)
496 {
497 assert(level >= 0);
498
499 while(level > 0)
500 {
501 component = component.getParent();
502 level--;
503 }
504 return component;
505 }
506
507 private transient UIComponent _commonParent;
508
509 private final String _movableChildScopedId;
510 private final String _sourceParentScopedId;
511 private final String _destinationContainerScopedId;
512 private final String _commonParentScopedId;
513 private final String _insertBeforeId;
514 private final String _sourceAbsoluteScopedId;
515 private final String _destinationAbsoluteScopedId;
516 private static final long serialVersionUID = 1L;
517
518 private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
519 MoveChildComponentChange.class);
520 }