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 }