Coverage Report - org.apache.myfaces.component.visit.PartialVisitContext
 
Classes in this File Line Coverage Branch Coverage Complexity
PartialVisitContext
0%
0/71
0%
0/38
2.19
PartialVisitContext$1
N/A
N/A
2.19
PartialVisitContext$CollectionProxy
0%
0/10
0%
0/2
2.19
PartialVisitContext$IteratorProxy
0%
0/12
0%
0/2
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  
 }