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 java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.LinkedHashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.faces.component.UIComponent;
28  import org.w3c.dom.Node;
29  import org.w3c.dom.NamedNodeMap;
30  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
31  
32  /**
33   * Change specialization for re-ordering of children.
34   * While applying this Change, the specified order of children is  restored.
35   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/change/ReorderChildrenComponentChange.java#0 $) $Date: 10-nov-2005.19:10:01 $
36   */
37  public class ReorderChildrenComponentChange extends ComponentChange
38                                              implements DocumentChange
39  {
40    /**
41     * Constructs a ReorderChange with the given List of identifiers for children.
42     * @param childIds An in-order collection (List) of Ids (as java.lang.String) 
43     *         of child components.
44     *        This List implementation should be of type java.io.Serializable in
45     *         order to be persisted.
46     *        If no identifier was passed, it would be assumed that the list 
47     *          consists of the Ids. 
48     * @throws IllegalArgumentException if supplied childIds were to be null.
49     */
50    public ReorderChildrenComponentChange(
51      List<String> childIds
52      )
53    {
54      this(childIds, "id");
55    }
56    
57    /**
58     * Constructs a ReorderChange with the given List of identifiers for children.
59     * @param childIds An in-order collection (List) of Ids (as java.lang.String) 
60     *         of child components.
61     *        This List implementation should be of type java.io.Serializable in
62     *         order to be persisted.
63     * @param identifier Determines the type of identifiers which the List consists of.
64     * @throws IllegalArgumentException if supplied childIds were to be null or supplied 
65     *          identifier was to be null or emtpy string.
66     */
67    public ReorderChildrenComponentChange(
68      List<String> childIds,
69      String identifier
70      )
71    {
72      if (childIds == null)
73        throw new IllegalArgumentException(_LOG.getMessage(
74          "CANNOT_CONSTRUCT_REORDERCHANGE_WITH_NULL_ID"));
75      
76      if (identifier == null || "".equals(identifier))
77        throw new IllegalArgumentException(_LOG.getMessage(
78          "IDENTIFIER_TYPE_CANNOT_BE_NULL"));
79      
80      // make serializable copy of list        
81      _childIds = Collections.unmodifiableList(new ArrayList<String>(childIds));
82      
83      _identifier = identifier;
84    }
85    
86    /**
87     * Returns an unmodifiable List of the identifiers for the  children.
88     */
89    public List<String> getChildIds()
90    {
91      return _childIds;
92    }
93    
94    /**
95     * Returns the identifier type.
96     */
97    public final String getIdentifier()
98    {
99      return _identifier;
100   }
101   
102   /**
103    * {@inheritDoc}
104    * In case children were to be removed between the time when this Change was
105    *  added, and the time when it was applied, maybe due to application of a
106    *  RemoveChildrenChange, such children are not re-instated.
107    * In case children were to be added between the time when this Change was
108    *  added, and the time when it was applied, maybe due to application of an 
109    *  AddChildChange, such children are appended to the end of the list in
110    *  preserving the order in which they were added (that is they appear at 
111    *  the end).
112    */
113   @SuppressWarnings("unchecked")
114   @Override
115   public void changeComponent(UIComponent uiComponent)
116   {
117     int childCount = uiComponent.getChildCount();
118     if (childCount == 0)
119       return;
120  
121     // build order map of of current Nodes, keyed by id
122     Map<String, UIComponent> childrenMap = new LinkedHashMap<String, UIComponent>();
123     
124     List<UIComponent> children = uiComponent.getChildren();
125     
126     int fakeIndex = 0;
127     for(UIComponent child : children)
128     {
129       String attrValue = (String)child.getAttributes().get(_identifier);
130       
131       // create a dummy key to maintain order of children whose identifier 
132       // does not exist
133       if (attrValue == null) 
134       {
135         attrValue = Integer.valueOf(fakeIndex++).toString();
136       }
137       childrenMap.put(attrValue, child);
138     }
139 
140     // remove the children so that we can add them back in
141     children.clear();
142 
143     //
144     // put children back in, in order
145     //
146     for(String currReorderID : _childIds)
147     {
148       UIComponent currChild = childrenMap.remove(currReorderID);
149       
150       if (currChild != null)
151       {
152         children.add(currChild);
153       }
154     }
155     
156     // add in all of the rest of the children in
157     // relative order they originally appeared
158     children.addAll(childrenMap.values());
159   }
160 
161   /**
162    * {@inheritDoc}
163    * In case children were to be removed between the time when this Change was
164    *  added, and the time when it was applied, maybe due to application of a
165    *  RemoveChildrenChange, such children are not re-instated.
166    * In case children were to be added between the time when this Change was
167    *  added, and the time when it was applied, maybe due to application of an 
168    *  AddChildChange, such children are appended to the end of the list in
169    *  preserving the order in which they were added (that is they appear at 
170    *  the end).
171    */
172   public void changeDocument(
173     Node componentNode)
174   {
175     // build order map of of current Nodes, keyed by id
176     LinkedHashMap<String, Node> currChildrenMap = new LinkedHashMap<String, Node>(13);
177         
178     Node currChild = componentNode.getFirstChild();
179     
180     int fakeIndex = 0;
181     while (currChild != null)
182     {
183       NamedNodeMap attributes = currChild.getAttributes();
184               
185       String currKey = null;
186       if (attributes != null)
187       {
188         Node idAttr = attributes.getNamedItem(_identifier);
189         
190         if (idAttr != null)
191         {
192           currKey = idAttr.getNodeValue();
193         }
194       }
195       
196       // create a dummy key to maintain order of non-ided children
197       if (currKey == null)
198       {
199         // =-= bts What about insignificant whitespace?
200         currKey = Integer.valueOf(fakeIndex++).toString();
201       }
202 
203       currChildrenMap.put(currKey, currChild);
204       
205       // remove the children so that we can add them back in
206       componentNode.removeChild(currChild);
207       
208       // next node is first node again
209       currChild = componentNode.getFirstChild();
210     }
211 
212     //
213     // put children back in, in order
214     //
215     for(String currReorderID : _childIds)
216     {
217       currChild = currChildrenMap.remove(currReorderID);
218       if (currChild != null)
219       {
220         componentNode.appendChild(currChild);
221       }
222     }
223     
224     // add in all of the rest of the children in
225     // relative order they originally appeared
226     for(Map.Entry<String, Node> entry : currChildrenMap.entrySet())
227     {
228       componentNode.appendChild(entry.getValue());
229     } 
230   }
231   
232   /** 
233    * Returns true if adding the DocumentChange should force the JSP Document
234    * to reload
235    */
236   public boolean getForcesDocumentReload()
237   {
238     return false;
239   }
240 
241   private final List<String> _childIds;
242   private final String _identifier;
243   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
244     ReorderChildrenComponentChange.class);
245   private static final long serialVersionUID = 1L;
246 }