/* * JBoss, Home of Professional Open Source. * Copyright 2009, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.jsfunit.jsfsession; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.faces.component.UIComponent; import javax.faces.component.UIData; import javax.faces.component.UIViewRoot; import javax.faces.component.ValueHolder; import javax.faces.context.FacesContext; import org.jboss.jsfunit.framework.FacesContextBridge; /** * This immutable helper class gathers all the client IDs from the current * component tree. It then allows finding a full client ID given a suffix of * that ID. This suffix is usually just the component ID, but for specificity * it can also include one or more of a component's naming containers such as in * "mysubview:myform:mycomponentID". * * @author Stan Silvert * @since 1.0 */ public class ClientIDs { private List<String> allClientIDs = new ArrayList<String>(); private Map<String, UIComponent> allComponents = new HashMap<String, UIComponent>(); // key = clientID; value = set of ancestor clientID's including my own private Map<String, Set> ancestors = new HashMap<String, Set>(); // key = clientID; value = the value manager for a component in a UIData private Map<String, UIDataValueManager> uiDataMap = new HashMap<String, UIDataValueManager>(); /** * Create a new instance of ClientIDs. */ ClientIDs() { FacesContext facesContext = FacesContextBridge.getCurrentInstance(); UIComponent component = facesContext.getViewRoot(); addAllIDs(component, facesContext); } // recursively walk the component tree and add all the client IDs to the list private void addAllIDs(UIComponent component, FacesContext facesContext) { if (component == null) return; addClientID(component, facesContext); newAncestorEntry(component, facesContext); if (component instanceof UIData) { addUIData((UIData)component, facesContext); return; } for (Iterator facetsAndChildren = component.getFacetsAndChildren(); facetsAndChildren.hasNext();) { UIComponent facetOrChild = (UIComponent)facetsAndChildren.next(); addAncestors(component, facetOrChild, facesContext); addAllIDs(facetOrChild, facesContext); } } private void newAncestorEntry(UIComponent component, FacesContext facesContext) { String clientID = component.getClientId(facesContext); if (ancestors.get(clientID) == null) { HashSet myAncestors = new HashSet(); myAncestors.add(clientID); // I am my own ancestor ancestors.put(clientID, myAncestors); } } private void addAncestors(UIComponent parent, UIComponent child, FacesContext facesContext) { if (child == null) return; addAncestors(parent.getClientId(facesContext), child.getClientId(facesContext)); } private void addAncestors(String parentClientID, String childClientID) { Set myAncestors = new HashSet(ancestors.get(parentClientID)); myAncestors.add(childClientID); // I am my own ancestor //System.out.println("ancestors: " + childClientID + " = " + myAncestors); ancestors.put(childClientID, myAncestors); } private void addUIData(UIData component, FacesContext facesContext) { String parentClientID = component.getClientId(facesContext); // TODO: find out if headers and footers are found int rowsToDisplay = component.getRows(); try { if (rowsToDisplay == 0) rowsToDisplay = component.getRowCount(); } catch (Exception e) { // ignore see JSFUNIT-215 } for (int i=0; i < rowsToDisplay; i++) { component.setRowIndex(i); addAncestors(parentClientID, component.getClientId(facesContext)); for (Iterator facetsAndChildren = component.getFacetsAndChildren(); facetsAndChildren.hasNext();) { UIComponent facetOrChild = (UIComponent)facetsAndChildren.next(); fillUIDataMap(component, facetOrChild, facesContext); addAncestors(component, facetOrChild, facesContext); addAllIDs(facetOrChild, facesContext); } } try { component.setRowIndex(-1); } catch (Exception e) { // ignore see JSFUNIT-215 } } private void fillUIDataMap(UIData uiData, UIComponent component, FacesContext facesContext) { updateUIDataMap(uiData, component, facesContext); for (Iterator facetsAndChildren = component.getFacetsAndChildren(); facetsAndChildren.hasNext();) { UIComponent facetOrChild = (UIComponent)facetsAndChildren.next(); fillUIDataMap(uiData, facetOrChild, facesContext); } } private void updateUIDataMap(UIData uiData, UIComponent facetOrChild, FacesContext facesContext) { if (!(facetOrChild instanceof ValueHolder)) return; String clientID = facetOrChild.getClientId(facesContext); if (uiDataMap.containsKey(clientID)) return; // optimization //System.out.println("**** Adding " + clientID + " to UIDataMap"); ValueHolder valueHolder = (ValueHolder)facetOrChild; this.uiDataMap.put(clientID, new UIDataValueManager(uiData, valueHolder)); //System.out.println("****"); } private void addClientID(UIComponent component, FacesContext facesContext) { if (component instanceof UIViewRoot) return; String clientId = component.getClientId(facesContext); if (clientId == null) return; // this can happen for facets if (allClientIDs.contains(clientId)) return; //System.out.println("adding clientID=" + clientId + "/ className=" + component.getClass().getName() + " / identity=" + component.hashCode()); allClientIDs.add(clientId); allComponents.put(clientId, component); } /** * Given a client ID or client ID suffix, find the fully-qualified client ID. * * @param suffix The full client ID or a suffix of the client ID. * * @return The fully-qualified client ID. * * @throws ComponentIDNotFoundException if no client ID matches the suffix * @throws DuplicateClientIDException if more than one client ID matches the suffix */ public String findClientID(String suffix) { if (suffix == null) throw new NullPointerException(); List<String> matches = new ArrayList<String>(); for (Iterator<String> ids = allClientIDs.iterator(); ids.hasNext();) { String id = ids.next(); if (id.endsWith(suffix)) matches.add(id); } if (matches.size() == 1) return matches.get(0); if (matches.isEmpty()) throw new ComponentIDNotFoundException(suffix); throw new DuplicateClientIDException(suffix, matches); } /** * Given a client ID suffix, find the matching UIComponent. * * @param suffix The full client ID or a suffix of the client ID. * * @return The UIComponent. * * @throws ComponentIDNotFoundException if no client ID matches the suffix * @throws DuplicateClientIDException if more than one client ID matches the suffix */ public UIComponent findComponent(String suffix) { return allComponents.get(findClientID(suffix)); } /** * Find a component in the JSF component tree and return its value. * Note that the found component must implement ValueHolder. * * @param componentID The JSF component ID or client ID suffix. * * @return The value contained in the component. * * @throws ComponentIDNotFoundException if the component can not be found * @throws DuplicateClientIDException if more than one client ID matches the componentID suffix * @throws ClassCastException if the found component does not implement ValueHolder */ public Object getComponentValue(String componentID) { UIComponent component = findComponent(componentID); if (!(component instanceof ValueHolder)) { throw new ClassCastException(componentID + " must be an instance of ValueHolder."); } String clientId = findClientID(componentID); if (uiDataMap.containsKey(clientId)) { return uiDataMap.get(clientId).getValue(); } return ((ValueHolder)component).getValue(); } /** * Determines if a component with a given clientID has an ancestor with a * given ancestorClientID. */ public boolean isAncestor(String clientID, String ancestorClientID) { Set ancestorSet = ancestors.get(clientID); if (ancestorSet == null) return false; return ancestorSet.contains(ancestorClientID); } /** * Debugging method that dumps all Client IDs to standard out. */ public void dumpAllIDs() { for (Iterator<String> i = this.allClientIDs.iterator(); i.hasNext();) { System.out.println(i.next()); } } }