View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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 id as the movable child were to be already present in the destination 
42   * container, the move operation is aborted.
43   * @see #add(FacesContext, ChangeManager)
44   * @see ChangeManager#addComponentChange(FacesContext, UIComponent, ComponentChange)
45   * @see ChangeManager#addDocumentChange(FacesContext, UIComponent, DocumentChange)
46   */
47  public final class MoveChildComponentChange 
48    extends ComponentChange
49    implements DocumentChange
50  {
51    /**
52     * Constructs a MoveChildComponentChange. The child will be appended to the 
53     * list of children of the destinationContainer.
54     * @param movableChild The child component to be moved.
55     * @param destinationContainer The destination component into which the child 
56     * component is to be moved.
57     * @throws IllegalArgumentException If movableChild or destinationContainer
58     * is null
59     */
60    public MoveChildComponentChange(
61      UIComponent movableChild,
62      UIComponent destinationContainer)
63    {
64      this(movableChild, destinationContainer, null);
65    }
66    
67    /**
68     * Constructs a MoveChildComponentChange. The child will be inserted to the 
69     * list of children of the destinationContainer, before the supplied 
70     * insertBeforecomponent. If the supplied insertBeforeComponent is null, the 
71     * child will be appended to the list of children of the destinationContainer.
72     * If the insertBeforeComponent is non-null, and if it were not to be found
73     * while applying this change, the movableChild will not be moved.
74     * @param movableChild The child component to be moved.
75     * @param destinationContainer The destination component into which the child 
76     * component is to be moved.
77     * @param insertBeforeComponent The component before which the moved child is
78     * to be inserted. This can be null, in which case the movableChild is
79     * appended.
80     * @throws IllegalArgumentException If movableChild or destinationContainer
81     * is null, or if a parent component common to movableChild and 
82     * destinationContainer could not be found.
83     */
84    public MoveChildComponentChange(
85      UIComponent movableChild,
86      UIComponent destinationContainer, 
87      UIComponent insertBeforeComponent)
88    {
89      if (movableChild == null)
90        throw new IllegalArgumentException(
91          _LOG.getMessage("MOVABLE_CHILD_REQUIRED"));
92  
93      if (destinationContainer == null)
94        throw new IllegalArgumentException(
95          _LOG.getMessage("DESTINATION_CONTAINER_REQUIRED"));
96      
97      UIComponent viewRoot = FacesContext.getCurrentInstance().getViewRoot();
98      
99      String sourceAbsoluteLogicalScopedId = ComponentUtils.getLogicalScopedIdForComponent(movableChild, viewRoot);
100     
101     String destinationContainerLogicalPrefix = _getScopedIdPrefix(destinationContainer,
102                                           ComponentUtils.getLogicalScopedIdForComponent(destinationContainer, viewRoot));
103     
104     String movableChildId = movableChild.getId();
105     
106     String destinationAbsoluteLogicalScopedId = (destinationContainerLogicalPrefix != null)
107                                           ? new StringBuilder(destinationContainerLogicalPrefix).
108                                             append(NamingContainer.SEPARATOR_CHAR).
109                                             append(movableChildId).toString()
110                                           : movableChildId;
111 
112     // Get the common parent
113     _commonParent = 
114       _getClosestCommonParentUIXComponent(movableChild, destinationContainer);
115     
116     if (_commonParent == null)
117       throw new IllegalArgumentException(
118         _LOG.getMessage("COMMON_PARENT_NOT_FOUND"));
119     
120     // Get the scoped id's for all participants
121     _movableChildScopedId = 
122       ComponentUtils.getScopedIdForComponent(movableChild, _commonParent);
123     _sourceParentScopedId = 
124       ComponentUtils.getScopedIdForComponent(movableChild.getParent(), 
125                                             _commonParent);
126     _destinationContainerScopedId = 
127       ComponentUtils.getScopedIdForComponent(destinationContainer, _commonParent);
128           
129     _commonParentScopedId = 
130       ComponentUtils.getScopedIdForComponent(_commonParent, viewRoot);
131     if (_movableChildScopedId == null || 
132         _sourceParentScopedId == null || 
133         _destinationContainerScopedId == null ||
134         _commonParentScopedId == null)
135       throw new IllegalArgumentException(
136         _LOG.getMessage("MOVE_PARTICIPANTS_WITHOUT_ID"));
137 
138     // calculate the absolute scoped ids for the source and destination so that we can
139     // handle remapping scoped ids in the SessionChangeManager    
140     String commonParentPrefix = _getScopedIdPrefix(_commonParent, _commonParentScopedId);
141       
142     _sourceAbsoluteScopedId = (commonParentPrefix != null)
143                                  ? new StringBuilder(commonParentPrefix).
144                                            append(NamingContainer.SEPARATOR_CHAR).
145                                            append(_movableChildScopedId).toString()
146                                  : _movableChildScopedId;
147     
148     _sourceAbsoluteLogicalScopedId = _sourceAbsoluteScopedId.equals(sourceAbsoluteLogicalScopedId) ? null : 
149                                                                                           sourceAbsoluteLogicalScopedId; 
150     
151     // calculate the absolute scoped id of the destination
152     String destinationContainerPrefix = _getScopedIdPrefix(destinationContainer,
153                                                            _destinationContainerScopedId);
154     
155     StringBuilder destinationScopedIdBuilder = new StringBuilder();
156     
157     if (commonParentPrefix != null)
158     {
159       destinationScopedIdBuilder.append(commonParentPrefix).append(NamingContainer.SEPARATOR_CHAR);
160     }
161     
162     if (destinationContainerPrefix != null)
163     {
164       destinationScopedIdBuilder.append(destinationContainerPrefix).append(NamingContainer.SEPARATOR_CHAR);
165     }
166     
167     _destinationAbsoluteScopedId = destinationScopedIdBuilder.append(movableChildId).toString();
168     
169     _destinationAbsoluteLogicalScopedId = _destinationAbsoluteScopedId.equals(destinationAbsoluteLogicalScopedId) ? null :
170                                               destinationAbsoluteLogicalScopedId;
171 
172     // For insertBeforeComponent, we do not care to obtain scoped id.
173     _insertBeforeId = (insertBeforeComponent == null) ? 
174       null:insertBeforeComponent.getId();
175   }
176   
177   private String _getScopedIdPrefix(UIComponent component, String scopedId)
178   {
179     if (component instanceof NamingContainer)
180       return scopedId;
181     else
182     {
183       // remove the component's id from the end
184       int separatorIndex = scopedId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
185       
186       if (separatorIndex >= 0)
187         return scopedId.substring(0, separatorIndex);
188       else
189       {
190         // component was at top level
191         return null;
192       }
193     }
194   }
195   
196   /**
197    * Convenience method to add this MoveChildComponentChange to the supplied
198    * ChangeManager. The change will be registered against a parent component
199    * that is common to the child being moved and the container component at
200    * destination.
201    * @param facesContext The FacesContext instance for the current request
202    * @param changeManager The ChangeManager instance on which this
203    * MoveChildComponentChange is to be added.
204    * @return The common parent component against which this 
205    * MoveChildComponentChange was registered.
206    */
207   public UIComponent add(
208     FacesContext facesContext, 
209     ChangeManager changeManager) 
210   {
211     UIComponent commonParent = _commonParent;
212 
213     if (commonParent == null)
214       commonParent = 
215         facesContext.getViewRoot().findComponent(_commonParentScopedId);
216     if (commonParent == null)
217     {
218       _LOG.warning("COMMON_PARENT_NOT_FOUND", _commonParentScopedId);
219       return null;
220     }
221     
222     // Register a move change against the common parent
223     changeManager.addComponentChange(facesContext, commonParent, this);
224     
225     // We dont need to keep the common parent anymore
226     _commonParent = null;
227     
228     return commonParent;
229   }
230    
231   /**
232    * Apply this change to the specified component.
233    * @param changeTargetComponent The component that is a common parent to the 
234    * movable child and the destination container.
235    * @throws IllegalArgumentException If the supplied changeTargetComponent
236    * is null.
237    */
238   @Override
239   public void changeComponent(UIComponent changeTargetComponent)
240   {
241     if (changeTargetComponent == null)
242       throw new IllegalArgumentException(
243         _LOG.getMessage("COMPONENT_REQUIRED"));
244     
245     // 1. Check for destination container component 
246     UIComponent destinationContainer = 
247       changeTargetComponent.findComponent(_destinationContainerScopedId);
248     if(destinationContainer == null)
249     {
250       _LOG.warning("DESTINATION_CONTAINER_NOT_FOUND", 
251                    _destinationContainerScopedId);
252       return;
253     }
254     
255     // 2. Find movableChild, gather the possible duplicates (theoritically only three) and keep a
256     //  single copy among them.
257     //  Duplicates are possible because 
258     //  a) taghandlers re-create the component that was in the jspx file in
259     //    their original location, no matter whether it was moved/removed due to 
260     //    aplication of a different ComponentChange. Such components could now 
261     //    be considered duplicates, because they are newly created from their vanilla definition
262     //    in the jspx document, and would not have any further ComponentChanges applied on them. 
263     //    There should be one such duplicate.
264     //  b) Apart from adding the MoveComponentChange, we expect developers to apply the change in 
265     //    order to reflect in the same request cycle (i.e. developers call 
266     //    ComponentChange.changeComponent()). Consequently, the component tree contains a moved 
267     //    child at the destination. Such components must be preserved, because they have 
268     //    incorporated any subsequent ComponentChanges on them. There should be one such moved
269     //    component.
270     //  c) We would have moved/added components due to previous customization an earlier application 
271     //    of ComponentChange, that could still be in the view tree. There should be one such zombie/ 
272     //    duplicate.
273     UIComponent sourceParent = 
274       changeTargetComponent.findComponent(_sourceParentScopedId);
275     
276     UIComponent foundChild = 
277       changeTargetComponent.findComponent(_movableChildScopedId);
278 
279     // To flag if a child was already found in a destination container (maybe due to previous move)    
280     boolean isChildIdAtDestination = false;
281     
282     UIComponent movableChild = null;
283     int movableChildIndex = 0;
284     UIComponent movedChild = null;
285     int movedChildIndex = 0;
286     UIComponent duplicateChild = null;
287     int duplicateChildIndex = 0;
288     UIComponent duplicateChildParent = null;
289 
290     while (foundChild != null)
291     {
292       // 2.a. If the parent matches, this could be the component that JSF-Runtime re-created
293       //  and added because it is in the physical document
294       if (foundChild.getParent().equals(sourceParent))
295       {
296         movableChild = foundChild;
297         movableChildIndex = sourceParent.getChildren().indexOf(movableChild);
298       }
299       // 2.b.a. We could possibly find the child at its destination, because apart from
300       //  adding the change, the change was applied in previous request, and the move
301       //  could have been within the same naming container umbrella. In this case
302       //  we do not want to move anything and the movable child is considered as a 
303       //  duplicate and candidate for removal.
304       else if (foundChild.getParent().equals(destinationContainer))
305       {
306         isChildIdAtDestination = true;
307         movedChild = foundChild;
308         movedChildIndex = destinationContainer.getChildren().indexOf(movedChild);
309       }
310       // 2.c. Possible dup from subsequent MoveChildComponentChange in the sequence of multiple
311       //  moves of the component in this same request. For example, if the move is from A->B->C,
312       //  and if we are currently dealing with move from A->B, the component that was added at
313       //  position C (in addition to adding the move change to changemanager) will now be dup.
314       else
315       {
316         duplicateChild = foundChild;
317         duplicateChildIndex = foundChild.getParent().getChildren().indexOf(foundChild);
318         duplicateChildParent = foundChild.getParent();
319       }
320 
321       // Invariably, remove the found component from the tree. We remove the
322       //  movableChild also, otherwise, findComponent blind loops on this same 
323       //  component if movableChild and duplicates are within same immediate
324       //  NamingContainer.
325       foundChild.getParent().getChildren().remove(foundChild);
326 
327       // Try and find the next potential copy of the component to move
328       foundChild = changeTargetComponent.findComponent(_movableChildScopedId);
329     }
330     
331     //  We need to re-attach the dup for now, the dupes will be eliminated gradually while applying
332     //  the successive move change involving the same component.
333     if (duplicateChild != null)
334     {
335       duplicateChildParent.getChildren().add(duplicateChildIndex, duplicateChild);
336     }
337 
338     // Can't do anything without a movable child.    
339     if(movableChild == null)
340     {
341       _LOG.warning("MOVABLE_CHILD_NOT_FOUND", _movableChildScopedId);
342       // Reverse any damage that we might have caused, and exit
343       if (movedChild != null)
344       {
345         destinationContainer.getChildren().add(movedChildIndex, movedChild);
346       }
347       return;
348     }
349     
350     // 2.b.b. Similar to situation in step #2.b.a, but here the move is across different naming 
351     //  containers, we could not catch this earlier.
352     if (!isChildIdAtDestination)
353     {
354       String movableChildId = movableChild.getId();
355       for (UIComponent childComponent:destinationContainer.getChildren())
356       {
357         if (movableChildId.equals(childComponent.getId()))
358         {
359           isChildIdAtDestination = true;
360           movedChild = childComponent;
361           // Temporarily remove this child, we might add it back in step #3 below.
362           movedChild.getParent().getChildren().remove(movedChild);
363           break;
364         }
365       }
366     }
367 
368     // 3. Check whether the destination container has a child with same id.
369     if (isChildIdAtDestination)
370     {
371       _LOG.warning("MOVABLE_CHILD_SAME_ID_FOUND", _movableChildScopedId);
372 
373       // Component type matches, this means the child is already at destination. We have removed all
374       //  duplicates, and have nothing more to do in this case
375       if ( (movableChild.getFamily().equals(movedChild.getFamily())) &&
376              (movableChild.getRendererType().equals(movedChild.getRendererType())) )
377       {
378         // Add back the moved child that we removed earlier.
379         destinationContainer.getChildren().add(movedChildIndex, movedChild);
380       }
381       else
382       {
383         // Duplicate child by id, but not of the same component type - a condition we cannot handle.
384         // Reverse any damage that we might have caused and exit
385         sourceParent.getChildren().add(movableChildIndex, movableChild);
386       }
387       return;
388     }
389 
390     // We are now dealing with case where there were no duplicates, and a proper point-to-point
391     //  move should happen. Reattach the moveable child, so that move happens atomically at the end.
392     sourceParent.getChildren().add(movableChildIndex, movableChild);
393     
394     // 4. See if we can find the insertBeforeComponent among the destinationContainer's children
395     int insertIndex = -1;
396     if (_insertBeforeId != null)
397     {
398       for (UIComponent childComponent:destinationContainer.getChildren())
399       {
400         if (_insertBeforeId.equals(childComponent.getId()))
401         {
402           insertIndex = 
403             destinationContainer.getChildren().indexOf(childComponent);
404           break;
405         }
406       }
407   
408       // insertBeforeId was specified, but we cannot find the insertBefore component. Exit.
409       if (insertIndex == -1)
410       {
411         _LOG.warning("INSERT_BEFORE_NOT_FOUND", _insertBeforeId);
412         return;
413       }
414     }
415     
416     // 5. Atomically move the child
417     if (insertIndex == -1)
418       destinationContainer.getChildren().add(movableChild);
419     else
420       destinationContainer.getChildren().add(insertIndex, movableChild);
421   }
422   
423   /**
424    * Given the DOM Node representing a Component, apply any necessary
425    * DOM changes. The node passed will be the Node that is a common parent for
426    * the movable child and the destination container.
427    * There is a limitation with the document change, that the movable child 
428    * Node, destination container Node, and the common parent Node have to belong
429    * to the same document.
430    * @param changeTargetNode DOM Node that is a common parent for the movable
431    * child and the destination container.
432    * @throws IllegalArgumentException If changeTargeNode were to be null.
433    */
434   public void changeDocument(Node changeTargetNode)
435   {
436     if (changeTargetNode == null)
437       throw new IllegalArgumentException(_LOG.getMessage("NO_NODE_SPECIFIED"));
438 
439     // Move involves four steps.
440     // 1. Finding the child node, the source of move
441     Node movableChildNode = 
442       ChangeUtils.__findNodeByScopedId(changeTargetNode, 
443                                        _movableChildScopedId, 
444                                        Integer.MAX_VALUE);
445     
446     if(movableChildNode == null)
447     {
448       _LOG.warning("MOVABLE_CHILD_NOT_FOUND", _movableChildScopedId);
449       return;
450     }
451     
452     // 2. Finding the destination container node
453     Node destinationContainerNode = 
454       ChangeUtils.__findNodeByScopedId(changeTargetNode, 
455                                        _destinationContainerScopedId, 
456                                        Integer.MAX_VALUE);
457 
458     
459     if(destinationContainerNode == null)
460     {
461       _LOG.warning("DESTINATION_CONTAINER_NOT_FOUND", 
462                    _destinationContainerScopedId);
463       return;
464     }
465     
466     //3. Finding the neighbor at the destination
467     Node insertBeforeNode = (_insertBeforeId == null) ? 
468       null:ChangeUtils.__findNodeByScopedId(destinationContainerNode, 
469                                             _insertBeforeId, 
470                                             1);
471     // insertBeforeId was specified, but corresponding component is missing.
472     //  Abort the move.
473     if(_insertBeforeId != null && insertBeforeNode == null)
474     {
475       _LOG.warning("INSERT_BEFORE_NOT_FOUND", _insertBeforeId);
476       return;
477     }
478 
479     //4. Atomically move the child.
480     destinationContainerNode.insertBefore(movableChildNode, insertBeforeNode);
481   }
482 
483   /** 
484    * Returns true if adding the DocumentChange should force the JSP Document
485    * to reload
486    * @return true Since moving of components should force the document to reload
487    */
488   public boolean getForcesDocumentReload()
489   {
490     return true;
491   }
492   
493   /**
494    * Returns the first UIXComponent common parent of two components in a
495    * subtree.
496    * @param firstComponent The first UIComponent instance
497    * @param secondComponent The second UIComponent instance
498    * @return UIComponent The closest common parent of the two supplied 
499    * components.
500    */
501   private static UIComponent _getClosestCommonParentUIXComponent(
502     UIComponent firstComponent,
503     UIComponent secondComponent) 
504   {
505     if (firstComponent == null || secondComponent == null)
506       return null;
507 
508     // Calculate the depth of each node.
509     int firstDepth = _computeDepth(firstComponent);
510     int secondDepth = _computeDepth(secondComponent);
511            
512     // Move the deeper of the two components to its ancestor at the same depth
513     // as the shallower.
514     if (secondDepth > firstDepth)
515     {
516       secondComponent = _getAncestor(secondComponent, secondDepth - firstDepth);
517     }
518     else if(secondDepth < firstDepth)
519     {
520       firstComponent = _getAncestor(firstComponent, firstDepth - secondDepth);
521     }
522 
523     // Crawl up until we find the shared ancestor.
524     while (firstComponent != null && (firstComponent != secondComponent))
525     {
526       firstComponent = firstComponent.getParent();
527       secondComponent = secondComponent.getParent();
528     }
529 
530     // Crawl up to first UIXComponent shared parent, since only UIXComponents 
531     // have tags that apply changes.
532     UIComponent sharedRoot = firstComponent;
533 
534     while ((sharedRoot != null) && !(sharedRoot instanceof UIXComponent))
535       sharedRoot = sharedRoot.getParent();
536           
537     return sharedRoot;
538   }
539   
540   /**
541    * Returns the absolute scopedId of the source component
542    */
543   public String getSourceScopedId()
544   {
545     return _sourceAbsoluteScopedId;
546   }
547 
548     
549   /**
550    * Returns the absolute scopedId of the source component at its destination
551    */
552   public String getDestinationScopedId()
553   {
554     return _destinationAbsoluteScopedId;
555   }
556   
557   
558   /**
559    * Returns the absolute logical scopedId of the source component
560    */
561   public String getSourceLogicalScopedId()
562   {
563     return (_sourceAbsoluteLogicalScopedId == null) ? _sourceAbsoluteScopedId : _sourceAbsoluteLogicalScopedId;
564   }
565   
566   /**
567    * Returns the absolute logical scopedId of the source component at its destination
568    */
569   public String getDestinationLogicalScopedId()
570   {
571     return (_destinationAbsoluteLogicalScopedId == null) ? _destinationAbsoluteScopedId : _destinationAbsoluteLogicalScopedId;
572   }
573   
574   @Override
575   public boolean equals(Object o)
576   {
577     if (o == this)
578       return true;
579     
580     if (!(o instanceof MoveChildComponentChange))
581       return false;
582     
583     MoveChildComponentChange other = (MoveChildComponentChange)o;
584     
585     return getSourceLogicalScopedId().equals(other.getSourceLogicalScopedId()) &&
586            getDestinationLogicalScopedId().equals(other.getDestinationLogicalScopedId()) &&
587            _equalsOrNull(_insertBeforeId, other._insertBeforeId);
588   }
589   
590   @Override
591   public int hashCode()
592   {
593     int hashCode = getSourceLogicalScopedId().hashCode() + 37 * getDestinationLogicalScopedId().hashCode();
594     if (_insertBeforeId != null)
595     {
596       hashCode = hashCode + 1369 * _insertBeforeId.hashCode();
597     }
598     return hashCode;
599   }
600       
601   @Override
602   public String toString()
603   {
604     return super.toString() + "[logical_source=" + getSourceLogicalScopedId() + " logical_destination=" + getDestinationLogicalScopedId() +
605             " absolute source=" + getSourceScopedId() + " absolute destination" + getDestinationScopedId() +" insert_before=" + _insertBeforeId + "]";
606   }
607   
608   /**
609    * Returns the depth of a UIComponent in the tree. 
610    * @param comp the UIComponent whose depth has to be calculated
611    * @return the depth of the passed in UIComponent
612    */
613   private static int _computeDepth(UIComponent comp) 
614   {
615     int i = 0;
616     while((comp = comp.getParent()) != null) 
617     {
618       i++;
619     }
620     return i;
621   }
622 
623   /**
624    * Returns the nth ancestor of the passed in component.
625    * @param component The UIComponent whose nth ancestor has to be found
626    * @param level Indicates how many levels to go up from the component
627    * @return The nth ancestor of the component
628    */
629   private static UIComponent _getAncestor(UIComponent component, int level) 
630   {
631     assert(level >= 0);
632     
633     while(level > 0)
634     {
635       component = component.getParent();
636       level--;
637     }
638     return component;
639   }
640   
641   private boolean _equalsOrNull(Object obj1, Object obj2)
642   {
643     return (obj1 == null) ? (obj2 == null) : obj1.equals(obj2);
644   }
645   
646   private transient UIComponent _commonParent;
647 
648   private final String _movableChildScopedId;
649   private final String _sourceParentScopedId;
650   private final String _destinationContainerScopedId;
651   private final String _commonParentScopedId;
652   private final String _insertBeforeId;
653   private final String _sourceAbsoluteScopedId;
654   private final String _destinationAbsoluteScopedId;
655   private final String _sourceAbsoluteLogicalScopedId;
656   private final String _destinationAbsoluteLogicalScopedId;
657   private static final long serialVersionUID = 1L;
658 
659   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
660     MoveChildComponentChange.class);
661 }