Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
PartialVisitContext |
|
| 2.1904761904761907;2.19 | ||||
PartialVisitContext$1 |
|
| 2.1904761904761907;2.19 | ||||
PartialVisitContext$CollectionProxy |
|
| 2.1904761904761907;2.19 | ||||
PartialVisitContext$IteratorProxy |
|
| 2.1904761904761907;2.19 |
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.component.visit; | |
20 | ||
21 | import java.util.AbstractCollection; | |
22 | import java.util.ArrayList; | |
23 | import java.util.Collection; | |
24 | import java.util.Collections; | |
25 | import java.util.EnumSet; | |
26 | import java.util.HashMap; | |
27 | import java.util.HashSet; | |
28 | import java.util.Iterator; | |
29 | import java.util.Map; | |
30 | import java.util.Set; | |
31 | ||
32 | import javax.faces.component.NamingContainer; | |
33 | import javax.faces.component.UIComponent; | |
34 | import javax.faces.component.visit.VisitCallback; | |
35 | import javax.faces.component.visit.VisitContext; | |
36 | import javax.faces.component.visit.VisitHint; | |
37 | import javax.faces.component.visit.VisitResult; | |
38 | import javax.faces.context.FacesContext; | |
39 | ||
40 | /** | |
41 | * <p>A VisitContext implementation that is | |
42 | * used when performing a partial component tree visit.</p> | |
43 | * | |
44 | * @author Werner Punz, Blake Sullivan (latest modification by $Author$) | |
45 | * @version $Rev$ $Date$ | |
46 | */ | |
47 | 0 | public class PartialVisitContext extends VisitContext |
48 | { | |
49 | ||
50 | /** | |
51 | * Creates a PartialVisitorContext instance. | |
52 | * @param facesContext the FacesContext for the current request | |
53 | * @param clientIds the client ids of the components to visit | |
54 | * @throws NullPointerException if {@code facesContext} | |
55 | * is {@code null} | |
56 | */ | |
57 | public PartialVisitContext( | |
58 | FacesContext facesContext, | |
59 | Collection<String> clientIds) | |
60 | { | |
61 | 0 | this(facesContext, clientIds, null); |
62 | 0 | } |
63 | ||
64 | /** | |
65 | * Creates a PartialVisitorContext instance with the specified hints. | |
66 | * @param facesContext the FacesContext for the current request | |
67 | * @param clientIds the client ids of the components to visit | |
68 | * @param hints a the VisitHints for this visit | |
69 | * @throws NullPointerException if {@code facesContext} | |
70 | * is {@code null} | |
71 | * @throws IllegalArgumentException if the phaseId is specified and | |
72 | * hints does not contain VisitHint.EXECUTE_LIFECYCLE | |
73 | */ | |
74 | public PartialVisitContext(FacesContext facesContext, | |
75 | Collection<String> clientIds, | |
76 | Set<VisitHint> hints) | |
77 | 0 | { |
78 | 0 | if (facesContext == null) |
79 | { | |
80 | 0 | throw new NullPointerException(); |
81 | } | |
82 | ||
83 | 0 | _facesContext = facesContext; |
84 | ||
85 | // Copy the client ids into a HashSet to allow for quick lookups. | |
86 | // Set<String> clientIdSet = (clientIds == null) | |
87 | // ? new HashSet<String>() | |
88 | // : new HashSet<String>(clientIds); | |
89 | ||
90 | // Initialize our various collections | |
91 | // We maintain 4 collections: | |
92 | // | |
93 | // 1. clientIds: contains all of the client ids to visit | |
94 | // 2. ids: contains just ids (not client ids) to visit. | |
95 | // We use this to optimize our check to see whether a | |
96 | // particular component is in the visit set (ie. to | |
97 | // avoid having to compute the client id). | |
98 | // 3. subtreeClientIds: contains client ids to visit broken | |
99 | // out by naming container subtree. (Needed by | |
100 | // getSubtreeIdsToVisit()). | |
101 | // 4. unvisitedClientIds: contains the client ids to visit that | |
102 | // have not yet been visited. | |
103 | // | |
104 | // We populate these now. | |
105 | // | |
106 | // Note that we use default HashSet/Map initial capacities, though | |
107 | // perhaps we could pick more intelligent defaults. | |
108 | ||
109 | // Initialize unvisitedClientIds collection | |
110 | 0 | _unvisitedClientIds = new HashSet<String>(); |
111 | ||
112 | // Initialize ids collection | |
113 | 0 | _ids = new HashSet<String>(); |
114 | ||
115 | // Intialize subtreeClientIds collection | |
116 | 0 | _subtreeClientIds = new HashMap<String, Collection<String>>(); |
117 | ||
118 | // Initialize the clientIds collection. Note that we proxy | |
119 | // this collection so that we can trap adds/removes and sync | |
120 | // up all of the other collections. | |
121 | 0 | _clientIds = new CollectionProxy<String>(new HashSet<String>()); |
122 | ||
123 | // Finally, populate the clientIds collection. This has the | |
124 | // side effect of populating all of the other collections. | |
125 | 0 | org.apache.myfaces.shared.util.ArrayUtils.addAll(_clientIds, clientIds); |
126 | //_clientIds.addAll(clientIdSet); | |
127 | ||
128 | // Copy and store hints - ensure unmodifiable and non-empty | |
129 | 0 | EnumSet<VisitHint> hintsEnumSet = ((hints == null) || (hints.isEmpty())) |
130 | ? EnumSet.noneOf(VisitHint.class) | |
131 | : EnumSet.copyOf(hints); | |
132 | ||
133 | 0 | _hints = Collections.unmodifiableSet(hintsEnumSet); |
134 | 0 | } |
135 | ||
136 | /** | |
137 | * @see VisitContext#getFacesContext VisitContext.getFacesContext() | |
138 | */ | |
139 | @Override | |
140 | public FacesContext getFacesContext() | |
141 | { | |
142 | 0 | return _facesContext; |
143 | } | |
144 | ||
145 | /** | |
146 | * @see VisitContext#getHints VisitContext.getHints | |
147 | */ | |
148 | @Override | |
149 | public Set<VisitHint> getHints() | |
150 | { | |
151 | 0 | return _hints; |
152 | } | |
153 | ||
154 | /** | |
155 | * @see VisitContext#getIdsToVisit VisitContext.getIdsToVisit() | |
156 | */ | |
157 | @Override | |
158 | public Collection<String> getIdsToVisit() | |
159 | { | |
160 | // We just return our clientIds collection. This is | |
161 | // the modifiable (but proxied) collection of all of | |
162 | // the client ids to visit. | |
163 | 0 | return _clientIds; |
164 | } | |
165 | ||
166 | /** | |
167 | * @see VisitContext#getSubtreeIdsToVisit VisitContext.getSubtreeIdsToVisit() | |
168 | */ | |
169 | @Override | |
170 | public Collection<String> getSubtreeIdsToVisit(UIComponent component) | |
171 | { | |
172 | // Make sure component is a NamingContainer | |
173 | 0 | if (!(component instanceof NamingContainer)) |
174 | { | |
175 | 0 | throw new IllegalArgumentException("Component is not a NamingContainer: " + component); |
176 | } | |
177 | ||
178 | 0 | String clientId = component.getClientId(getFacesContext()); |
179 | 0 | Collection<String> ids = _subtreeClientIds.get(clientId); |
180 | ||
181 | 0 | if (ids == null) |
182 | { | |
183 | 0 | return Collections.emptyList(); |
184 | } | |
185 | else | |
186 | { | |
187 | 0 | return Collections.unmodifiableCollection(ids); |
188 | } | |
189 | } | |
190 | ||
191 | /** | |
192 | * @see VisitContext#invokeVisitCallback VisitContext.invokeVisitCallback() | |
193 | */ | |
194 | @Override | |
195 | public VisitResult invokeVisitCallback( | |
196 | UIComponent component, | |
197 | VisitCallback callback) | |
198 | { | |
199 | // First sure that we should visit this component - ie. | |
200 | // that this component is represented in our id set. | |
201 | 0 | String clientId = _getVisitId(component); |
202 | ||
203 | 0 | if (clientId == null) |
204 | { | |
205 | // Not visiting this component, but allow visit to | |
206 | // continue into this subtree in case we've got | |
207 | // visit targets there. | |
208 | 0 | return VisitResult.ACCEPT; |
209 | } | |
210 | ||
211 | // If we made it this far, the component matches one of | |
212 | // client ids, so perform the visit. | |
213 | 0 | VisitResult result = callback.visit(this, component); |
214 | ||
215 | // Remove the component from our "unvisited" collection | |
216 | 0 | _unvisitedClientIds.remove(clientId); |
217 | ||
218 | // If the unvisited collection is now empty, we are done. | |
219 | // Return VisitResult.COMPLETE to terminate the visit. | |
220 | 0 | if (_unvisitedClientIds.isEmpty()) |
221 | { | |
222 | 0 | return VisitResult.COMPLETE; |
223 | } | |
224 | else | |
225 | { | |
226 | // Otherwise, just return the callback's result | |
227 | 0 | return result; |
228 | } | |
229 | } | |
230 | ||
231 | ||
232 | // Called by CollectionProxy to notify PartialVisitContext that | |
233 | // an new id has been added. | |
234 | private void _idAdded(String clientId) | |
235 | { | |
236 | // An id to visit has been added, update our other | |
237 | // collections to reflect this. | |
238 | ||
239 | // Update the ids collection | |
240 | 0 | _ids.add(_getIdFromClientId(clientId)); |
241 | ||
242 | // Update the unvisited ids collection | |
243 | 0 | _unvisitedClientIds.add(clientId); |
244 | ||
245 | // Update the subtree ids collection | |
246 | 0 | _addSubtreeClientId(clientId); |
247 | 0 | } |
248 | ||
249 | // Called by CollectionProxy to notify PartialVisitContext that | |
250 | // an id has been removed | |
251 | private void _idRemoved(String clientId) | |
252 | { | |
253 | // An id to visit has been removed, update our other | |
254 | // collections to reflect this. Note that we don't | |
255 | // update the ids collection, since we ids (non-client ids) | |
256 | // may not be unique. | |
257 | ||
258 | // Update the unvisited ids collection | |
259 | 0 | _unvisitedClientIds.remove(clientId); |
260 | ||
261 | // Update the subtree ids collection | |
262 | 0 | _removeSubtreeClientId(clientId); |
263 | 0 | } |
264 | ||
265 | // Tests whether the specified component should be visited. | |
266 | // If so, returns its client id. If not, returns null. | |
267 | private String _getVisitId(UIComponent component) | |
268 | { | |
269 | // We first check to see whether the component's id | |
270 | // is in our id collection. We do this before checking | |
271 | // for the full client id because getting the full client id | |
272 | // is more expensive than just getting the local id. | |
273 | 0 | String id = component.getId(); |
274 | ||
275 | 0 | if ((id != null) && !_ids.contains(id)) |
276 | { | |
277 | 0 | return null; |
278 | } | |
279 | ||
280 | // The id was a match - now check the client id. | |
281 | // note that client id should never be null (should be | |
282 | // generated even if id is null, so asserting this.) | |
283 | 0 | String clientId = component.getClientId(getFacesContext()); |
284 | 0 | assert(clientId != null); |
285 | ||
286 | 0 | return _clientIds.contains(clientId) ? clientId : null; |
287 | } | |
288 | ||
289 | ||
290 | ||
291 | // Converts an client id into a plain old id by ripping | |
292 | // out the trailing id segmetn. | |
293 | private String _getIdFromClientId(String clientId) | |
294 | { | |
295 | 0 | final char separator = getFacesContext().getNamingContainerSeparatorChar(); |
296 | 0 | int lastIndex = clientId.lastIndexOf(separator); |
297 | ||
298 | 0 | String id = null; |
299 | ||
300 | 0 | if (lastIndex < 0) |
301 | { | |
302 | 0 | id = clientId; |
303 | } | |
304 | 0 | else if (lastIndex < (clientId.length() - 1)) |
305 | { | |
306 | 0 | id = clientId.substring(lastIndex + 1); |
307 | } | |
308 | //else | |
309 | //{ | |
310 | // TODO log warning for trailing colon case | |
311 | //} | |
312 | ||
313 | 0 | return id; |
314 | } | |
315 | ||
316 | ||
317 | // Given a single client id, populate the subtree map with all possible | |
318 | // subtree client ids | |
319 | private void _addSubtreeClientId(String clientId) | |
320 | { | |
321 | // Loop over the client id and find the substring corresponding to | |
322 | // each ancestor NamingContainer client id. For each ancestor | |
323 | // NamingContainer, add an entry into the map for the full client | |
324 | // id. | |
325 | 0 | final char separator = getFacesContext().getNamingContainerSeparatorChar(); |
326 | ||
327 | 0 | int length = clientId.length(); |
328 | ||
329 | 0 | for (int i = 0; i < length; i++) |
330 | { | |
331 | 0 | if (clientId.charAt(i) == separator) |
332 | { | |
333 | // We found an ancestor NamingContainer client id - add | |
334 | // an entry to the map. | |
335 | 0 | String namingContainerClientId = clientId.substring(0, i); |
336 | ||
337 | // Check to see whether we've already ids under this | |
338 | // NamingContainer client id. If not, create the | |
339 | // Collection for this NamingContainer client id and | |
340 | // stash it away in our map | |
341 | 0 | Collection<String> c = _subtreeClientIds.get(namingContainerClientId); |
342 | ||
343 | 0 | if (c == null) |
344 | { | |
345 | // TODO: smarter initial size? | |
346 | 0 | c = new ArrayList<String>(); |
347 | 0 | _subtreeClientIds.put(namingContainerClientId, c); |
348 | } | |
349 | ||
350 | // Stash away the client id | |
351 | 0 | c.add(clientId); |
352 | } | |
353 | } | |
354 | 0 | } |
355 | ||
356 | // Given a single client id, remove any entries corresponding | |
357 | // entries from our subtree collections | |
358 | private void _removeSubtreeClientId(String clientId) | |
359 | { | |
360 | // Loop through each entry in the map and check to see whether | |
361 | // the client id to remove should be contained in the corresponding | |
362 | // collection - ie. whether the key (the NamingContainer client id) | |
363 | // is present at the start of the client id to remove. | |
364 | 0 | for (String key : _subtreeClientIds.keySet()) |
365 | { | |
366 | 0 | if (clientId.startsWith(key)) |
367 | { | |
368 | // If the clientId starts with the key, we should | |
369 | // have an entry for this clientId in the corresponding | |
370 | // collection. Remove it. | |
371 | 0 | Collection<String> ids = _subtreeClientIds.get(key); |
372 | 0 | ids.remove(clientId); |
373 | } | |
374 | 0 | } |
375 | 0 | } |
376 | ||
377 | // Little proxy collection implementation. We proxy the id | |
378 | // collection so that we can detect modifications and update | |
379 | // our internal state when ids to visit are added or removed. | |
380 | 0 | private class CollectionProxy<E extends String> extends AbstractCollection<E> |
381 | { | |
382 | private CollectionProxy(Collection<E> wrapped) | |
383 | 0 | { |
384 | 0 | _wrapped = wrapped; |
385 | 0 | } |
386 | ||
387 | @Override | |
388 | public int size() | |
389 | { | |
390 | 0 | return _wrapped.size(); |
391 | } | |
392 | ||
393 | @Override | |
394 | public Iterator<E> iterator() | |
395 | { | |
396 | 0 | return new IteratorProxy<E>(_wrapped.iterator()); |
397 | } | |
398 | ||
399 | @Override | |
400 | public boolean add(E o) | |
401 | { | |
402 | 0 | boolean added = _wrapped.add(o); |
403 | ||
404 | 0 | if (added) |
405 | { | |
406 | 0 | _idAdded(o); |
407 | } | |
408 | ||
409 | 0 | return added; |
410 | } | |
411 | ||
412 | private final Collection<E> _wrapped; | |
413 | } | |
414 | ||
415 | // Little proxy iterator implementation used by CollectionProxy | |
416 | // so that we can catch removes. | |
417 | 0 | private class IteratorProxy<E extends String> implements Iterator<E> |
418 | { | |
419 | private IteratorProxy(Iterator<E> wrapped) | |
420 | 0 | { |
421 | 0 | _wrapped = wrapped; |
422 | 0 | } |
423 | ||
424 | public boolean hasNext() | |
425 | { | |
426 | 0 | return _wrapped.hasNext(); |
427 | } | |
428 | ||
429 | public E next() | |
430 | { | |
431 | 0 | _current = _wrapped.next(); |
432 | ||
433 | 0 | return _current; |
434 | } | |
435 | ||
436 | public void remove() | |
437 | { | |
438 | 0 | if (_current != null) |
439 | { | |
440 | 0 | _idRemoved(_current); |
441 | } | |
442 | ||
443 | 0 | _wrapped.remove(); |
444 | 0 | } |
445 | ||
446 | private final Iterator<E> _wrapped; | |
447 | ||
448 | 0 | private E _current = null; |
449 | } | |
450 | ||
451 | // The client ids to visit | |
452 | private final Collection<String> _clientIds; | |
453 | ||
454 | // The ids to visit | |
455 | private final Collection<String> _ids; | |
456 | ||
457 | // The client ids that have yet to be visited | |
458 | private final Collection<String> _unvisitedClientIds; | |
459 | ||
460 | // This map contains the information needed by getIdsToVisit(). | |
461 | // The keys in this map are NamingContainer client ids. The values | |
462 | // are collections containing all of the client ids to visit within | |
463 | // corresponding naming container. | |
464 | private final Map<String,Collection<String>> _subtreeClientIds; | |
465 | ||
466 | // The FacesContext for this request | |
467 | private final FacesContext _facesContext; | |
468 | ||
469 | // Our visit hints | |
470 | private final Set<VisitHint> _hints; | |
471 | } |