com.sun.faces.component.visit.PartialVisitContext Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.faces.component.visit;
import javax.faces.component.visit.*;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.NamingContainer;
import javax.faces.context.FacesContext;
/**
*
* A VisitContext implementation that is
* used when performing a partial component tree visit.
*
* RELEASE_PENDING
* @since 2.0
*/
public class PartialVisitContext extends VisitContext {
/**
* Creates a PartialVisitorContext instance.
* @param facesContext the FacesContext for the current request
* @param clientIds the client ids of the components to visit
* @throws NullPointerException if {@code facesContext}
* is {@code null}
*/
public PartialVisitContext(FacesContext facesContext,
Collection clientIds) {
this(facesContext, clientIds, null);
}
/**
* Creates a PartialVisitorContext instance with the specified hints.
* @param facesContext the FacesContext for the current request
* @param clientIds the client ids of the components to visit
* @param hints a the VisitHints for this visit
* @throws NullPointerException if {@code facesContext}
* is {@code null}
*/
public PartialVisitContext(FacesContext facesContext,
Collection clientIds,
Set hints) {
if (facesContext == null) {
throw new NullPointerException();
}
this.facesContext = facesContext;
// Initialize our various collections
initializeCollections(clientIds);
// Copy and store hints - ensure unmodifiable and non-empty
EnumSet hintsEnumSet = ((hints == null) || (hints.isEmpty()))
? EnumSet.noneOf(VisitHint.class)
: EnumSet.copyOf(hints);
this.hints = Collections.unmodifiableSet(hintsEnumSet);
}
/**
* @see VisitContext#getFacesContext VisitContext.getFacesContext()
*/
@Override
public FacesContext getFacesContext() {
return facesContext;
}
/**
* @see VisitContext#getHints VisitContext.getHints
*/
@Override
public Set getHints() {
return hints;
}
/**
* @see VisitContext#getIdsToVisit VisitContext.getIdsToVisit()
*/
@Override
public Collection getIdsToVisit() {
// We just return our clientIds collection. This is
// the modifiable (but proxied) collection of all of
// the client ids to visit.
return clientIds;
}
public Collection getUnvisitedClientIds() {
return unvisitedClientIds;
}
/**
* @see VisitContext#getSubtreeIdsToVisit VisitContext.getSubtreeIdsToVisit()
*/
@Override
public Collection getSubtreeIdsToVisit(UIComponent component) {
// Make sure component is a NamingContainer
if (!(component instanceof NamingContainer)) {
throw new IllegalArgumentException("Component is not a NamingContainer: " + component);
}
String clientId = component.getClientId();
Collection ids = subtreeClientIds.get(clientId);
if (ids == null)
return Collections.emptyList();
return Collections.unmodifiableCollection(ids);
}
/**
* @see VisitContext#invokeVisitCallback VisitContext.invokeVisitCallback()
*/
@Override
public VisitResult invokeVisitCallback(UIComponent component,
VisitCallback callback) {
// First sure that we should visit this component - ie.
// that this component is represented in our id set.
String clientId = getVisitId(component);
if (clientId == null) {
// Not visiting this component, but allow visit to
// continue into this subtree in case we've got
// visit targets there.
return VisitResult.ACCEPT;
}
// If we made it this far, the component matches one of
// client ids, so perform the visit.
VisitResult result = callback.visit(this, component);
// Remove the component from our "unvisited" collection
unvisitedClientIds.remove(clientId);
// If the unvisited collection is now empty, we are done.
// Return VisitResult.COMPLETE to terminate the visit.
if (unvisitedClientIds.isEmpty())
return VisitResult.COMPLETE;
// Otherwise, just return the callback's result
return result;
}
// Called by CollectionProxy to notify PartialVisitContext that
// an new id has been added.
private void idAdded(String clientId) {
// An id to visit has been added, update our other
// collections to reflect this.
// Update the ids collection
ids.add(getIdFromClientId(clientId));
// Update the unvisited ids collection
unvisitedClientIds.add(clientId);
// Update the subtree ids collection
addSubtreeClientId(clientId);
}
// Called by CollectionProxy to notify PartialVisitContext that
// an id has been removed
private void idRemoved(String clientId) {
// An id to visit has been removed, update our other
// collections to reflect this. Note that we don't
// update the ids collection, since we ids (non-client ids)
// may not be unique.
// Update the unvisited ids collection
unvisitedClientIds.remove(clientId);
// Update the subtree ids collection
removeSubtreeClientId(clientId);
}
// Called to initialize our various collections.
private void initializeCollections(Collection clientIds) {
// We maintain 4 collections:
//
// 1. clientIds: contains all of the client ids to visit
// 2. ids: contains just ids (not client ids) to visit.
// We use this to optimize our check to see whether a
// particular component is in the visit set (ie. to
// avoid having to compute the client id).
// 3. subtreeClientIds: contains client ids to visit broken
// out by naming container subtree. (Needed by
// getSubtreeIdsToVisit()).
// 4. unvisitedClientIds: contains the client ids to visit that
// have not yet been visited.
//
// We populate these now.
//
// Note that we use default HashSet/Map initial capacities, though
// perhaps we could pick more intelligent defaults.
// Initialize unvisitedClientIds collection
this.unvisitedClientIds = new HashSet();
// Initialize ids collection
this.ids = new HashSet();
// Intialize subtreeClientIds collection
this.subtreeClientIds = new HashMap>();
// Initialize the clientIds collection. Note that we proxy
// this collection so that we can trap adds/removes and sync
// up all of the other collections.
this.clientIds = new CollectionProxy(new HashSet());
// Finally, populate the clientIds collection. This has the
// side effect of populating all of the other collections.
this.clientIds.addAll(clientIds);
}
// Tests whether the specified component should be visited.
// If so, returns its client id. If not, returns null.
private String getVisitId(UIComponent component) {
// We first check to see whether the component's id
// is in our id collection. We do this before checking
// for the full client id because getting the full client id
// is more expensive than just getting the local id.
String id = component.getId();
if ((id != null) && !ids.contains(id))
return null;
// The id was a match - now check the client id.
// note that client id should never be null (should be
// generated even if id is null, so asserting this.)
String clientId = component.getClientId();
assert(clientId != null);
return clientIds.contains(clientId) ? clientId : null;
}
// Converts an client id into a plain old id by ripping
// out the trailing id segmetn.
private String getIdFromClientId(String clientId)
{
FacesContext facesContext = getFacesContext();
char separator = UINamingContainer.getSeparatorChar(facesContext);
int lastIndex = clientId.lastIndexOf(separator);
String id = null;
if (lastIndex < 0) {
id = clientId;
} else if (lastIndex < (clientId.length() - 1)) {
id = clientId.substring(lastIndex + 1);
}
return id;
}
// Given a single client id, populate the subtree map with all possible
// subtree client ids
private void addSubtreeClientId(String clientId) {
FacesContext facesContext = getFacesContext();
char separator = UINamingContainer.getSeparatorChar(facesContext);
// Loop over the client id and find the substring corresponding to
// each ancestor NamingContainer client id. For each ancestor
// NamingContainer, add an entry into the map for the full client
// id.
int length = clientId.length();
for (int i = 0; i < length; i++) {
if (clientId.charAt(i) == separator) {
// We found an ancestor NamingContainer client id - add
// an entry to the map.
String namingContainerClientId = clientId.substring(0, i);
// Check to see whether we've already ids under this
// NamingContainer client id. If not, create the
// Collection for this NamingContainer client id and
// stash it away in our map
Collection c = subtreeClientIds.get(namingContainerClientId);
if (c == null) {
c = new ArrayList();
subtreeClientIds.put(namingContainerClientId, c);
}
// Stash away the client id
c.add(clientId);
}
}
}
// Given a single client id, remove any entries corresponding
// entries from our subtree collections
private void removeSubtreeClientId(String clientId) {
// Loop through each entry in the map and check to see whether
// the client id to remove should be contained in the corresponding
// collection - ie. whether the key (the NamingContainer client id)
// is present at the start of the client id to remove.
for (String key : subtreeClientIds.keySet()) {
if (clientId.startsWith(key)) {
// If the clientId starts with the key, we should
// have an entry for this clientId in the corresponding
// collection. Remove it.
Collection ids = subtreeClientIds.get(key);
ids.remove(clientId);
}
}
}
// Little proxy collection implementation. We proxy the id
// collection so that we can detect modifications and update
// our internal state when ids to visit are added or removed.
private class CollectionProxy extends
AbstractCollection {
private CollectionProxy(Collection wrapped) {
this.wrapped = wrapped;
}
@Override
public int size() {
return wrapped.size();
}
@Override
public Iterator iterator() {
return new IteratorProxy(wrapped.iterator());
}
@Override
public boolean add(E o) {
boolean added = wrapped.add(o);
if (added) {
idAdded(o);
}
return added;
}
private Collection wrapped;
}
// Little proxy iterator implementation used by CollectionProxy
// so that we can catch removes.
private class IteratorProxy implements Iterator {
private IteratorProxy(Iterator wrapped) {
this.wrapped = wrapped;
}
public boolean hasNext() {
return wrapped.hasNext();
}
public E next() {
current = wrapped.next();
return current;
}
public void remove() {
if (current != null) {
idRemoved(current);
}
wrapped.remove();
}
private Iterator wrapped;
private E current = null;
}
// The client ids to visit
private Collection clientIds;
// The ids to visit
private Collection ids;
// The client ids that have yet to be visited
private Collection unvisitedClientIds;
// This map contains the information needed by getSubtreeIdsToVisit().
// The keys in this map are NamingContainer client ids. The values
// are collections containing all of the client ids to visit within
// corresponding naming container.
private Map> subtreeClientIds;
// The FacesContext for this request
private FacesContext facesContext;
// Our visit hints
private Set hints;
}