/* * 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.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 javax.faces.webapp; import javax.faces.FacesException; import javax.faces.application.Application; import javax.faces.component.NamingContainer; import javax.faces.component.UIComponent; import javax.faces.component.UIOutput; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.BodyContent; import javax.servlet.jsp.tagext.BodyTag; import javax.servlet.jsp.tagext.JspIdConsumer; import javax.servlet.jsp.tagext.Tag; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.RandomAccess; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; /** * <p><strong><code>UIComponentTagBase</code></strong> is the base class * for all JSP tags that use the "classic" JSP tag interface that * correspond to a {@link javax.faces.component.UIComponent} instance in * the view. In Faces 1.2, all component tags are <code>BodyTag</code> * instances to allow for the execution of the page to build the * component tree, but not render it. Rendering happens only after the * component tree is completely built.</p> * * <p>{@link UIComponentTag} extends * <code>UIComponentClassicTagBase</code> to add support for properties * that conform to the Faces 1.1 EL.</p> * * <p>{@link UIComponentELTag} extends * <code>UIComponentClassicTagBase</code> class to add support for * properties that conform to the EL API.</p> * * <p>The default implementation allows the proper interweaving of * template text, non-Faces JSP tag output, and Faces component tag * output in the same page, as expected by the page author.</p> * * <p>The CASE markers in the following example will be cited in the * method descriptions of this class.</p> * * <ul> * * <li><p>CASE 1 describes template text and/or non-component custom tag * output occurring as the child of a component tag, but before the * first component tag child of that component tag.</p></li> * * <li><p>CASE 2 describes template text and/or non-component custom tag * output occurring between two sibling component tags.</p></li> * * <li><p>CASE 3 describes template text and/or non-component custom tag * output occurring as the child content of an <f:verbatim> * tag at any point in the page.</p></li> * * <li><p>CASE 4 describes template text and/or non-component custom tag * output occurring between the last child component tag and its * enclosing parent component tag's end tag.</p></li> * * </ul> * * <pre><code> <h:panelGrid style="color:red" border="4" columns="2"> CASE 1 <h:outputText value="component 1"/> CASE 2 <h:outputText value="component 2"/> <f:verbatim>CASE 3</f:verbatim> <c:out value="${pageScope.CASE4}" /> </h:panelGrid> * </code></pre> * * <p>The preceding arrangement of faces component tags, must yield * markup that will render identically to the following (assuming that * <code>${pageScope.CASE4}</code> evaluates to "<code>CASE 4</code>" * without the quotes).</p> * * <pre><code> <table border="4" style="color:red"> <tbody> <tr><td>CASE 1</td></tr> <tr><td>component 1</td></tr> <tr><td>CASE 2</td> <tr><td>component 2</td></tr> <tr><td>CASE 3</td> <td>CASE 4</td></tr> </tbody> </table> * </code></pre> * */ public abstract class UIComponentClassicTagBase extends UIComponentTagBase implements JspIdConsumer, BodyTag { // ------------------------------------------------------ Manifest Constants /** * <p>The facesContext scope attribute under which a component tag stack * for the current facesContext will be maintained.</p> */ private static final String COMPONENT_TAG_STACK_ATTR = "javax.faces.webapp.COMPONENT_TAG_STACK"; /** * <p>The {@link UIComponent} attribute under which we will store a * <code>List</code> of the component identifiers of child components * created on the previous generation of this page (if any).</p> */ private static final String JSP_CREATED_COMPONENT_IDS = "javax.faces.webapp.COMPONENT_IDS"; /** * <p>The {@link UIComponent} attribute under which we will store a * <code>List</code> of the facet names of facets created on the previous * generation of this page (if any). */ private static final String JSP_CREATED_FACET_NAMES = "javax.faces.webapp.FACET_NAMES"; /** * <p>The attribute name under which we will store all {@link UIComponent} * IDs of the current translation unit.</p> */ private static final String GLOBAL_ID_VIEW = "javax.faces.webapp.GLOBAL_ID_VIEW"; /** * <p>The attribute name under which we will store the {@link FacesContext} * for this request.</p> */ private static final String CURRENT_FACES_CONTEXT = "javax.faces.webapp.CURRENT_FACES_CONTEXT"; /** * <p>The attribute name under which we will store the {@link UIViewRoot} * for this request.</p> */ private static final String CURRENT_VIEW_ROOT = "javax.faces.webapp.CURRENT_VIEW_ROOT"; /** * Used as the prefix for ids. This is necessary to avoid * uniqueness conflicts with the transient verbatim components. */ protected static final String UNIQUE_ID_PREFIX = UIViewRoot.UNIQUE_ID_PREFIX + '_'; /** * Used to store the previousJspId Map in facesContextScope */ private static final String PREVIOUS_JSP_ID_SET = "javax.faces.webapp.PREVIOUS_JSP_ID_SET"; /** * This is a <code>Page</code> scoped marker to help us * keep track of the different execution context we could * be operating within, e.g. an include, or a tag file. * The value of the attribute is an Integer that is unqiue * to this page context. */ private static final String JAVAX_FACES_PAGECONTEXT_MARKER = "javax.faces.webapp.PAGECONTEXT_MARKER"; /** * This is a <code>facesContext</code> scoped attribute which contains * an AtomicInteger which we use to increment the PageContext * count. */ private static final String JAVAX_FACES_PAGECONTEXT_COUNTER = "javax.faces.webapp.PAGECONTEXT_COUNTER"; // ------------------------------------------------------ Instance Variables /** * <p>The <code>bodyContent</code> for this tag handler.</p> */ protected BodyContent bodyContent = null; /** * <p>The {@link UIComponent} that is being encoded by this tag, * if any.</p> */ private UIComponent component = null; /** * <p>The {@link FacesContext} for the request being processed, if any. * </p> */ private FacesContext context = null; /** * <p>Was a new component instance dynamically created when our * <code>findComponent()</code> method was called.</p> */ private boolean created = false; /** * <p>The <code>Lst</code> of {@link UIComponent} ids created or located * by nested {@link UIComponentTag}s while processing the current * request.</p> */ private List<String> createdComponents = null; /** * <p>The <code>List</code> of facet names created or located by nested * {@link UIComponentTag}s while processing the current request.</p> */ private List<String> createdFacets = null; /** * <p>The JSP <code>PageContext</code> for the page we are embedded in.</p> */ protected PageContext pageContext = null; /** * <p>The JSP <code>Tag</code> that is the parent of this tag.</p> */ private Tag parent = null; /** * {@link #setJspId} */ private String jspId = null; /** * Only consulted in setJspId to detect the iterator case. * Set in {@link #release}. Never cleared. */ //private String oldJspId = null; /** * This is simply the jspId prefixed by {@link #UNIQUE_ID_PREFIX}. */ private String facesJspId = null; /** * <p>The component identifier for the associated component.</p> */ private String id = null; /** * Caches the nearest enclosing {@link UIComponentClassicTagBase} of this * tag. This is used for duplicate id detection. */ private UIComponentClassicTagBase parentTag = null; /** * Set to true if this component is nested inside of an iterating * tag */ private boolean isNestedInIterator = false; /** * The next child index to get in getChild() */ private int _nextChildIndex = 0; Map<String, Map<String, UIComponentTagBase>> namingContainerChildIds = null; public UIComponentClassicTagBase() {} UIComponentClassicTagBase(PageContext pageContext, FacesContext facesContext) { this.pageContext = pageContext; this.context = facesContext; } // --------------------------------------------- Support Methods for Tag // // Simple methods to be overridden by subclasses if necessary // /** * <p>Return the flag value that should be returned from the * <code>doStart()</code> method when it is called. Subclasses * may override this method to return the appropriate value.</p> * * @throws JspException to cause <code>doStart()</code> to * throw an exception * * @return the value to return from <code>doStart()</code> */ protected int getDoStartValue() throws JspException { int result = EVAL_BODY_BUFFERED; return result; } /** * <p>Return the flag value that should be returned from the * <code>doEnd()</code> method when it is called. Subclasses * may override this method to return the appropriate value.</p> * * @throws JspException to cause <code>doEnd()</code> to * throw an exception * * @return the value to return from <code>doEnd()</code> */ protected int getDoEndValue() throws JspException { return (EVAL_PAGE); } /** * <p>Delegate to the <code>encodeBegin()</code> method of our * corresponding {@link UIComponent}. This method is called from * <code>doStartTag()</code>. Normally, delegation occurs unconditionally; * however, this method is abstracted out so that advanced tags can * conditionally perform this call. * * @throws IOException if an input/output error occurs * * @deprecated No encoding is done during JSP page execution. * Encoding is deferred until the page has completed executing to * allow the entire tree to be built before any encoding occurs. */ protected void encodeBegin() throws IOException { component.encodeBegin(context); } /** * <p>Delegate to the <code>encodeChildren()</code> method of our * corresponding {@link UIComponent}. This method is called from * <code>doStartTag()</code>. Normally, delegation occurs unconditionally; * however, this method is abstracted out so that advanced tags can * conditionally perform this call. * * @throws IOException if an input/output error occurs * * @deprecated No encoding is done during JSP page execution. * Encoding is deferred until the page has completed executing to * allow the entire tree to be built before any encoding occurs. */ protected void encodeChildren() throws IOException { component.encodeChildren(context); } /** * <p>Delegate to the <code>encodeEnd()</code> method of our * corresponding {@link UIComponent}. This method is called from * <code>doStartTag()</code>. Normally, delegation occurs unconditionally; * however, this method is abstracted out so that advanced tags can * conditionally perform this call. * * @throws IOException if an input/output error occurs * * @deprecated No encoding is done during JSP page execution. * Encoding is deferred until the page has completed executing to * allow the entire tree to be built before any encoding occurs. */ protected void encodeEnd() throws IOException { component.encodeEnd(context); } // --------------------------------------------------------- Tag Properties /** * <p>Set the <code>PageContext</code> of the page containing this * tag instance.</p> * * @param pageContext The enclosing <code>PageContext</code> */ @Override public void setPageContext(PageContext pageContext) { this.pageContext = pageContext; } /** * <p>Return the <code>Tag</code> that is the parent of this instance.</p> */ @Override public Tag getParent() { return (this.parent); } /** * <p>Set the <code>Tag</code> that is the parent of this instance.</p> * * @param parent The new parent <code>Tag</code> */ @Override public void setParent(Tag parent) { this.parent = parent; } // // Complex methods to support Tag // /** * <p>Set up the {@link javax.faces.context.ResponseWriter} for the * current response, if this has not been done already.</p> * * <p>@deprecated. {@link * javax.faces.application.ViewHandler#renderView} is now * responsible for setting up the response writer. This method is * now a no-op.</p> */ protected void setupResponseWriter() { } /** * <p>Create a new child component using <code>createComponent</code>, * initialize its properties, and add it to its parent as a child. * </p> * @param context {@link FacesContext} for the current request * @param parent Parent {@link UIComponent} for the new child * @param componentId Component identifier for the new child, * or <code>null</code> for no explicit identifier */ private UIComponent createChild( FacesContext context, UIComponent parent, UIComponentClassicTagBase parentTag, String componentId) throws JspException { UIComponent component = createComponent(context, componentId); int indexOfNextChildTag = parentTag.getIndexOfNextChildTag(); if (indexOfNextChildTag > parent.getChildCount()) { indexOfNextChildTag = parent.getChildCount(); } parent.getChildren().add(indexOfNextChildTag, component); created = true; return (component); } /** * <p>Create a new child component using <code>createComponent</code>, * initialize its properties, and add it to its parent as a facet. * </p> * @param context {@link FacesContext} for the current request * @param parent Parent {@link UIComponent} of the new facet * @param name Name of the new facet * @param newId id of the new facet */ private UIComponent createFacet(FacesContext context, UIComponent parent, String name, String newId) throws JspException { UIComponent component = createComponent(context, newId); parent.getFacets().put(name, component); created = true; return (component); } /** * <p>Return a child with the specified component id from the specified * component, if any; otherwise, return <code>null</code>.</p> * * @param component {@link UIComponent} to be searched * @param componentId Component id to search for */ private static UIComponent getChild( UIComponentClassicTagBase tag, UIComponent component, String componentId) { int childCount = component.getChildCount(); // we only need to bother to check if we even have children if (childCount > 0) { List<UIComponent> children = component.getChildren(); // Most Lists implement RandomAccess, so iterate directly rather than creating // and iterator if (children instanceof RandomAccess) { // in the most common case, the first component we are asked for will be the // our first child, the second, our second, etc. Take advantage of this by // remembering the index to check for the next child. This changes this code // from O(n^2) for all of the children to O(n) int startIndex; if (tag != null) startIndex = tag._nextChildIndex; else startIndex = 0; // start searching from location remembered from last time for (int i = startIndex; i < childCount; i++) { UIComponent child = children.get(i); if (componentId.equals(child.getId())) { // bump up the index to search next and wrap around i++; tag._nextChildIndex = (i < childCount) ? i : 0; return child; } } // handle case where we started past the first item and didn't find our // child. Now search from the beginning to where we started if (startIndex > 0) { for (int i = 0; i < startIndex; i++) { UIComponent child = children.get(i); if (componentId.equals(child.getId())) { i++; tag._nextChildIndex = i; return child; } } } } else { // List doesn't support RandomAccess, do it the iterator way for (UIComponent child : children) { if (componentId.equals(child.getId())) return child; } } } return null; } /** * <p>Find and return the {@link UIComponent}, from the component * tree, that corresponds to this tag handler instance. If there * is no such {@link UIComponent}, create one * and add it as a child or facet of the {@link UIComponent} associated * with our nearest enclosing {@link UIComponentTag}. The process for * locating or creating the component is:</p> * <ol> * <li>If we have previously located this component, return it.</li> * <li>Locate the parent component by looking for a parent * {@link UIComponentTag} instance, and ask it for its component. * If there is no parent {@link UIComponentTag} instance, this tag * represents the root component, so get it from the current * <code>Tree</code> and return it.</li> * <li>If this {@link UIComponentTag} instance has the * <code>facetName</code> attribute set, ask the parent * {@link UIComponent} for a facet with this name. If not found, * create one, call <code>setProperties()</code> with the new * component as a parameter, and register it under this name. * Return the found or created facet {@link UIComponent}.</li> * <li>Determine the component id to be assigned to the new * component, as follows: if this {@link UIComponentTag} has * an <code>id</code> attribute set, use that value; otherwise, * generate an identifier that is guaranteed to be the same for * this {@link UIComponent} every time this page is processed * (i.e. one based on the location of all {@link UIComponentTag} * instances without an <code>id</code> attribute set).</li> * <li>Ask the parent {@link UIComponent} for a child with this identifier. * If not found, create one, call <code>setProperties()</code> * with the new component as a parameter, and register it as a child * with this identifier. Return the found or created * child {@link UIComponent}.</li> * </ol> * <p>When creating a component, the process is:</p> * <ol> * <li>Retrieve the component type by calling * {@link UIComponentTag#getComponentType}</li> * <li>If the component has a <code>binding</code> attribute, * create an expression from it, and call * {@link Application#createComponent} with that expression, * the {@link FacesContext}, and the component type. Store the * expression using the key <code>"binding"</code>.</li> * <li>Otherwise, call {@link Application#createComponent} with * only the component type. * <li>Call <code>setProperties()</code>. * <li>Add the new component as a child or facet of its parent</li> * </ol> * * @param context the {@code FacesContext} for the current request. * * @return the found component * * @throws JspException if an unexpected condition arises while finding the component */ protected UIComponent findComponent(FacesContext context) throws JspException { // Step 1 -- Have we already found the relevant component? if (component != null) { return (component); } // Step 2 -- Identify the component that is, or will be, our parent UIComponentClassicTagBase parentTag = _getParentUIComponentClassicTagBase(context.getAttributes()); UIComponent parentComponent; if (parentTag != null) { parentComponent = parentTag.getComponentInstance(); } else { // Special case. The component to be found is the // UIViewRoot. // see if this is the first time this tag instance is trying // to be bound to the UIViewRoot parentComponent = context.getViewRoot(); // Has this UIViewRoot instance had a tag bound to it // before? if (null == parentComponent.getAttributes().get(CURRENT_VIEW_ROOT)) { // No it hasn't. // make sure setProperties() and setId() are called // once per UIViewRoot instance. try { setProperties(parentComponent); } catch (FacesException e) { if (e.getCause() instanceof JspException) { throw ((JspException)e.getCause()); } throw e; } if (null != this.id) { parentComponent.setId(this.id); } else { assert(null != getFacesJspId()); parentComponent.setId(getFacesJspId()); } parentComponent.getAttributes().put(CURRENT_VIEW_ROOT, CURRENT_VIEW_ROOT); created = true; } else if (hasBinding()) { try { setProperties(parentComponent); } catch (FacesException e) { if (e.getCause() instanceof JspException) { throw ((JspException)e.getCause()); } throw e; } } // this is not the first time this tag instance is trying to // be bound to this UIViewRoot, take no extra action. component = parentComponent; return (component); } // Step 3 -- Calculate the component identifier for this component String newId = createId(context); // Step 4 -- Create or return a facet with the specified name (if any) String facetName = getFacetName(); boolean created = parentTag.getCreated(); if (facetName != null) { component = parentComponent.getFacets().get(facetName); if (component == null) { component = createFacet(context, parentComponent, facetName, newId); } return (component); } else { // Step 5 -- Create or return a child with the specified id component = getChild(parentTag, parentComponent, newId); if (component == null) { component = createChild(context, parentComponent, parentTag, newId); } return (component); } } // // Tag tree navigation // /** * <p>Locate and return the nearest enclosing {@link UIComponentClassicTagBase} * if any; otherwise, return <code>null</code>.</p> * * @param context <code>PageContext</code> for the current page * * @return the parent tag */ public static UIComponentClassicTagBase getParentUIComponentClassicTagBase(PageContext context) { return _getParentUIComponentClassicTagBase(getFacesContext(context)); } private static UIComponentClassicTagBase _getParentUIComponentClassicTagBase( FacesContext facesContext) { return _getParentUIComponentClassicTagBase(facesContext.getAttributes()); } private static UIComponentClassicTagBase _getParentUIComponentClassicTagBase(Map<Object, Object> cMap) { List list = null; if (cMap != null) { list = (List) cMap.get(COMPONENT_TAG_STACK_ATTR); } if (list != null) { return ((UIComponentClassicTagBase) list.get(list.size() - 1)); } else { return null; } } // // Methods related to the createdComponents and createdFacets lists. // @Override protected int getIndexOfNextChildTag() { if (createdComponents != null) { return (createdComponents.size()); } else { return (0); } } @Override protected void addChild(UIComponent child) { if (createdComponents == null) { createdComponents = new ArrayList<>(6); } createdComponents.add(child.getId()); } /* * Adds argument child to component tree as a child of this component. */ void addChildToComponentAndTag(UIComponent child) { UIComponent myComponent = this.getComponentInstance(); int indexOfNextChildTag = this.getIndexOfNextChildTag(); if (indexOfNextChildTag > myComponent.getChildCount()) { indexOfNextChildTag = myComponent.getChildCount(); } myComponent.getChildren().add(indexOfNextChildTag, child); this.addChild(child); } @Override protected void addFacet(String name) { if (createdFacets == null) { //noinspection CollectionWithoutInitialCapacity createdFacets = new ArrayList<>(3); } createdFacets.add(name); } /** * <p>Pop the top {@link UIComponentTag} instance off of our component tag * stack, deleting the stack if this was the last entry.</p> */ private void popUIComponentClassicTagBase() { List list = (List) context.getAttributes().get(COMPONENT_TAG_STACK_ATTR); // if an exception occurred in a nested tag, //there could be a few tags left in the stack. UIComponentClassicTagBase uic = null; while (list != null && uic != this) { int idx = list.size() - 1; uic = (UIComponentClassicTagBase) list.get(idx); list.remove(idx); if (idx < 1) { context.getAttributes().remove(COMPONENT_TAG_STACK_ATTR); list = null; } } } /** * <p>Push the specified {@link UIComponentTag} instance onto our component * tag stack, creating a stack if necessary.</p> */ private void pushUIComponentClassicTagBase() { List<UIComponentClassicTagBase> list = TypedCollections.dynamicallyCastList((List) context.getAttributes().get(COMPONENT_TAG_STACK_ATTR), UIComponentClassicTagBase.class); if (list == null) { //noinspection CollectionWithoutInitialCapacity list = new ArrayList<>(); context.getAttributes().put(COMPONENT_TAG_STACK_ATTR, list); } list.add(this); } /** * Similar to List.indexOf, except that we start searching from a specific index * and then wrap aroud. For this to be performant, the List should implement * RandomAccess. * @param <T> * @param list List to seatch * @param startIndex index to start searching for value from * @param searchValue Value to search for (null not supported) * @return The index at which the value was first found, or -1 if not found */ private static int _indexOfStartingFrom(List<?> list, int startIndex, Object searchValue) { int itemCount = list.size(); boolean found = false; // start searching from location remembered from last time for (int currIndex = startIndex; currIndex < itemCount; currIndex++) { Object currId = list.get(currIndex); if ((searchValue == currId) || ((searchValue != null) && searchValue.equals(currId))) { return currIndex; } } // handle case where we started past the first item and didn't find the // searchValue. Now search from the beginning to where we started if (startIndex > 0) { for (int currIndex = 0; currIndex < startIndex; currIndex++) { Object currId = list.get(currIndex); if ((searchValue == currId) || ((searchValue != null) && searchValue.equals(currId))) { return currIndex; } } } // didn't find it return -1; } /** * <p>Retrieve from the {@link UIComponent} corresponding to this tag * handler the list of all child component ids created by * {@link UIComponentTag} instances the previous time this tree was * rendered. Compare it to the list of children created during this * page processing pass, and remove all children present on the old list * but not in the new list. Save the list as a {@link UIComponent} * attribute so that it gets saved as part of the component's state.</p> */ private void removeOldChildren() { Map<String, Object> attributes = component.getAttributes(); List<String> currentComponents = createdComponents; // Get the old list of created component ids and update the current list as a // component attribute Object oldValue; if (currentComponents != null) { oldValue = attributes.put(JSP_CREATED_COMPONENT_IDS, currentComponents); createdComponents = null; } else { oldValue = attributes.remove(JSP_CREATED_COMPONENT_IDS); } // Remove old children that are no longer present if (oldValue != null) { List<String> oldList = TypedCollections.dynamicallyCastList((List)oldValue, String.class); int oldCount = oldList.size(); if (oldCount > 0) { if (currentComponents != null) { int currStartIndex = 0; for (int oldIndex = 0; oldIndex < oldCount; oldIndex++) { String oldId = oldList.get(oldIndex); int foundIndex = _indexOfStartingFrom(currentComponents, currStartIndex, oldId); if (foundIndex != -1) { currStartIndex = foundIndex + 1; } else { UIComponent child = component.findComponent(oldId); // if a component is marked transient, it would have // been already removed from the child list, but the // oldList would still have it. In addition, the component // might have manually been removed. So, if findComponent // isn't successful, don't call remove child (it will NPE) if ( child != null) { component.getChildren().remove(child); } } } } else { List<UIComponent> children = component.getChildren(); // All old components need to be removed for (String oldId : oldList) { UIComponent child = component.findComponent(oldId); if (child != null) { children.remove(child); } } } } } } /** * <p>Retrieve from the {@link UIComponent} corresponding to this tag * handler the list of all facet names created by {@link UIComponentTag} * instances the previous time this tree was rendered. Compare it to the * list of facets created during this page processing pass, and remove * all facets present on the old list but not in the new list. Save the * list as a {@link UIComponent} attribute so that it gets saved as part * of the component's state.</p> */ private void removeOldFacets() { Map<String, Object> attributes = component.getAttributes(); List<String> currentComponents = createdFacets; // Get the old list of created component ids and update the current list as a // component attribute Object oldValue; if (currentComponents != null) { oldValue = attributes.put(JSP_CREATED_FACET_NAMES, currentComponents); createdFacets = null; } else { oldValue = attributes.remove(JSP_CREATED_FACET_NAMES); } // Remove old children that are no longer present if (oldValue != null) { List<String> oldList = TypedCollections.dynamicallyCastList((List)oldValue, String.class); int oldCount = oldList.size(); if (oldCount > 0) { if (currentComponents != null) { int currStartIndex = 0; for (int oldIndex = 0; oldIndex < oldCount; oldIndex++) { String oldId = oldList.get(oldIndex); int foundIndex = _indexOfStartingFrom(currentComponents, currStartIndex, oldId); if (foundIndex != -1) { currStartIndex = foundIndex + 1; } else { component.getFacets().remove(oldId); } } } else { Map<String, UIComponent> facets = component.getFacets(); // All old facets need to be removed for (String oldId : oldList) { facets.remove(oldId); } } } } } // // Methods to support content interweaving // /** * * <p>Create a transient UIOutput component from the body content, * of this tag instance or return null if there is no body content, * the body content is whitespace, or the body content is a * comment.</p> * * @return the component */ protected UIComponent createVerbatimComponentFromBodyContent() { UIOutput verbatim = null; String bodyContentString; String trimString; if (null != bodyContent && null != (bodyContentString = bodyContent.getString()) && 0 < (trimString = bodyContent.getString().trim()).length()) { if (!(trimString.startsWith("<!--") && trimString.endsWith("-->"))) { verbatim = createVerbatimComponent(); verbatim.setValue(bodyContentString); bodyContent.clearBody(); } else { StringBuilder content = new StringBuilder(trimString.length()); int sIdx = trimString.indexOf("<!--"); int eIdx = trimString.indexOf("-->", sIdx); while (sIdx >= 0 && eIdx >= 0) { if (sIdx == 0) { trimString = trimString.substring(eIdx + 3); } else { content.append(trimString.substring(0, sIdx)); trimString = trimString.substring(eIdx + 3); } sIdx = trimString.indexOf("<!--"); eIdx = trimString.indexOf("-->", sIdx); } content.append(trimString); String result = content.toString(); if (result.trim().length() > 0) { verbatim = createVerbatimComponent(); verbatim.setValue(content.toString()); } bodyContent.clearBody(); } } return verbatim; } /** * <p>Use the {@link Application} instance to create a new component * with the following characteristics.</p> * * <p><code>componentType</code> is * <code>javax.faces.HtmlOutputText</code>.</p> * * <p><code>transient</code> is <code>true</code>.</p> * * <p><code>escape</code> is <code>false</code>.</p> * * <p><code>id</code> is * <code>FacesContext.getViewRoot().createUniqueId()</code></p> * * * @return the component */ protected UIOutput createVerbatimComponent() { assert(null != getFacesContext()); UIOutput verbatim; Application application = getFacesContext().getApplication(); verbatim = (UIOutput) application.createComponent("javax.faces.HtmlOutputText"); verbatim.setTransient(true); verbatim.getAttributes().put("escape", Boolean.FALSE); verbatim.setId(getFacesContext().getViewRoot().createUniqueId()); return verbatim; } /** * <p>Add <i>verbatim</i> as a sibling of <i>component</i> in * <i>component</i> in the parent's child list. <i>verbatim</i> is * added to the list at the position immediatly preceding * <i>component</i>.</p> * * * @param parentTag the parent tag * * @param verbatim the verbatim to add before the component * * @param component the component to be added after the component * */ protected void addVerbatimBeforeComponent( UIComponentClassicTagBase parentTag, UIComponent verbatim, UIComponent component) { UIComponent parent = component.getParent(); if (null == parent) { return; } List<UIComponent> children = parent.getChildren(); // EDGE CASE: // Consider CASE 1 or 2 where the component is provided via a // component binding in session or application scope. // The automatically created UIOuput instances for the template text // will already be present. Check the JSP_CREATED_COMPONENT_IDS attribute, // if present and the number of created components is the same // as the number of children replace at a -1 offset from the current // value of indexOfComponentInParent, otherwise, call add() List createdIds = (List) parent.getAttributes().get(JSP_CREATED_COMPONENT_IDS); int indexOfComponentInParent = children.indexOf(component); boolean replace = (indexOfComponentInParent > 0 && createdIds != null && createdIds.size() == children.size()); if (replace) { UIComponent oldVerbatim = children.get(indexOfComponentInParent - 1); if (oldVerbatim instanceof UIOutput && oldVerbatim.isTransient()) { children.set((indexOfComponentInParent - 1), verbatim); } else { children.add(indexOfComponentInParent, verbatim); } } else { children.add(indexOfComponentInParent, verbatim); } parentTag.addChild(verbatim); } /** * <p>Add <i>verbatim</i> as a sibling of <i>component</i> in * <i>component</i> in the parent's child list. <i>verbatim</i> is * added to the list at the position immediatly following * <i>component</i>.</p> * * @param parentTag the parent tag * * @param verbatim the verbatim to add after the component * * @param component the component to be added before the component */ protected void addVerbatimAfterComponent(UIComponentClassicTagBase parentTag, UIComponent verbatim, UIComponent component) { int indexOfComponentInParent; UIComponent parent = component.getParent(); // invert the order of this if and the assignment below. Since this line is // here, it appears an early return is acceptable/desired if parent is null, // and, if it is null, we should probably check for that before we try to // access it. 2006-03-15 jdl if (null == parent) { return; } List<UIComponent> children = parent.getChildren(); indexOfComponentInParent = children.indexOf(component); if (children.size() - 1 == indexOfComponentInParent) { children.add(verbatim); } else { children.add(indexOfComponentInParent + 1, verbatim); } parentTag.addChild(verbatim); } // ------------------------------------------------------------ Tag Methods /** * * <p>Perform any processing necessary to find (or create) the * {@link UIComponent} instance in the view corresponding to this * tag instance in the page and, if and only if a component was * created, insert it into the tree at the proper location as * expected by the page author. Secondarily, cause a transient * {@link UIOutput} component to be created and placed in the tree * <b>before</b> the <code>UIComponent</code> instance for * <b>this</b> tag. The value of this <code>UIOutput</code> * component must include anything covered by <code>CASE 1</code> or * <code>CASE 2</code> in the class description.</p> * * <p>The default implementation, which is intended to be sufficient * for most components, implements this secondary requirement by * calling {@link #getParentUIComponentClassicTagBase}, and calling * {@link #createVerbatimComponentFromBodyContent} on the result. * It then adds the returned component to the tree <b>before</b> the * actual component for <b>this</b> tag instance instance by calling * {@link #addVerbatimBeforeComponent}.</p> * * <p>Before returning, the component is pushed onto the component * stack for this response to enable the {@link * #getParentUIComponentClassicTagBase} method to work properly.</p> * * <p>The flag value to be returned is acquired by calling the * <code>getDoStartValue()</code> method, which tag subclasses may * override if they do not want the default value.</p> * * @throws JspException if an error occurs */ @Override public int doStartTag() throws JspException { // make sure that these ivars are reset at the beginning of the // lifecycle for this tag. createdComponents = null; createdFacets = null; UIComponent verbatim = null; context = getFacesContext(); if (null == context) { // PENDING(edburns): I18N throw new JspException("Can't find FacesContext"); } List list = (List) context.getAttributes().get(COMPONENT_TAG_STACK_ATTR); if (list != null) { parentTag = ((UIComponentClassicTagBase) list.get(list.size() - 1)); } else { parentTag = null; } Map<String,UIComponentTagBase> componentIds; // If we're not inside of a facet, and if we are inside of a // rendersChildren==true component, stuff any template text or // custom tag output into a transient component. if (null == getFacetName() && null != parentTag) { Tag p = this.getParent(); // If we're not inside a JSP tag or we're not inside // a UIComponentTag flush the buffer if (null == p || !(p instanceof UIComponentTagBase)) { JspWriter out = pageContext.getOut(); if (!(out instanceof BodyContent)) { try { out.flush(); } catch (IOException ioe) { throw new JspException(ioe); } } } verbatim = parentTag.createVerbatimComponentFromBodyContent(); } // Locate the UIComponent associated with this UIComponentTag, // creating one if necessary component = findComponent(context); // if we have a verbatim component, add it after this component. if (null != verbatim) { addVerbatimBeforeComponent(parentTag, verbatim, component); } Object tagInstance = null; String clientId = null; if (component instanceof NamingContainer || (parentTag == null)) { namingContainerChildIds = new HashMap<>(); } if (this.id != null) { clientId = getId(); UIComponentClassicTagBase temp = (UIComponentClassicTagBase) getParentNamingContainerTag().getNamingContainerChildIds().get(clientId); // According to the JavaDocs for JspIdConsumer tag handlers // that implement this interface are not to be pooled, however // due to a bug in Jasper this is not the case. // Because of this, two tags with the same ID within the same // naming container will not be detected as duplicates. So // in order to ensure we detect it, if the instance is the same, // verify the JSP IDs are different. If they are, then continue, // if they aren't, then we're dealing with EVAL_BODY_AGAIN (see // below) //noinspection ObjectEquality if (temp == this && !this.getJspId().equals(temp.getJspId())) { tagInstance = this; } else if (temp != null && temp != this && this.getJspId().equals(temp.getJspId())) { // new instance, same JSP ID - this is the EVAL_BODY_AGAIN case. tagInstance = temp; } } // If we have a tag instance, then, most likely, a tag handler // returned EVAL_BODY_AGAIN somewhere. Make sure the instance // returned is the same as the current instance and if this is the case, // do not perform ID validation as it has already been done. if (tagInstance == null) { // only check for id uniqueness if the author has manually given // us an id. if (null != this.id) { // assert component ID uniqueness if (clientId != null) { if (getParentNamingContainerTag().getNamingContainerChildIds().containsKey(clientId)) { // PENDING i18n StringWriter writer = new StringWriter(128); printTree(context.getViewRoot(), clientId, writer, 0); String msg = "Duplicate component id: '" + clientId + "', first used in tag: '" + getParentNamingContainerTag().getNamingContainerChildIds().get(clientId).getClass().getName() + "'\n" + writer.toString(); throw new JspException(new IllegalStateException(msg)); } else { getParentNamingContainerTag().getNamingContainerChildIds().put(clientId, this); } } } // Add to parent's list of created components or facets if needed if (parentTag != null) { if (getFacetName() == null) { parentTag.addChild(component); } else { parentTag.addFacet(getFacetName()); } } } // Rendering is deferred until after the tree is completely // created // Return the appropriate control value pushUIComponentClassicTagBase(); return (getDoStartValue()); } /** * * <p>Perform any processing necessary to handle the content * implications of CASE 3 in the class description.</p> * * <p>The default implementation, which is intended to be sufficient * for most components, calls {@link * #createVerbatimComponentFromBodyContent} on <b>this</b> instance * and adds it as a child of the component for this tag's component * at the <b>end</b> of the child list. In addition, the following * housekeeping steps are taken.</p> * * <ul> * * <li>Retrieve from the {@link UIComponent} the set of component * ids of child components created by {@link UIComponentTag} * instances the last time this page was processed (if any). * Compare it to the list of children created during this page * processing pass, and remove all children present in the old list * but not the new. Save the new list as a component attribute so * that it gets saved as part of the component's state.</li> * * <li>Retrieve from the {@link UIComponent} the set of facet names * of facets created by {@link UIComponentTag} instances the last * time this page was processed (if any). Compare it to the list of * facets created during this page processing pass, and remove all * facets present in the old list but not the new. Save the new * list as a component attribute so that it gets saved as part of * the component's state.</li> * * <li>Release all references to the component, and pop it from the * component stack for this response, removing the stack if this was * the outermost component.</li> </ul> * * <p>The flag value to be returned is acquired by calling the * <code>getDoEndValue()</code> method, which tag subclasses may * override if they do not want the default value.</p> * * @throws JspException if an error occurs */ @Override public int doEndTag() throws JspException { // Remove old children and facets as needed popUIComponentClassicTagBase(); removeOldChildren(); removeOldFacets(); //If we are at the end tag of a NamingContainer component, reset the Map of ids // for the NamingContainer tag. if (namingContainerChildIds != null) { namingContainerChildIds = null; } // Render the children (if needed) and end of the component // associated with this tag try { UIComponent verbatim; UIComponentClassicTagBase parentTag = _getParentUIComponentClassicTagBase( context.getAttributes()); if (null != (verbatim = this.createVerbatimComponentFromBodyContent())) { component.getChildren().add(verbatim); if (null != parentTag) { parentTag.addChild(verbatim); } } // else, we don't render rendersChildren==true // components here } catch (Throwable e) { throw new JspException(e); } finally { component = null; context = null; } this.release(); return (getDoEndValue()); } /** * <p>Release any resources allocated during the execution of this * tag handler.</p> */ @Override public void release() { this.parent = null; this.id = null; this.facesJspId = null; this.created = false; this.bodyContent = null; this.isNestedInIterator = false; _nextChildIndex = 0; } // -------------------------------------------- Support methods for BodyTag /** * <p>Return the flag value that should be returned from the * <code>doAfterBody()</code> method when it is called. Subclasses * may override this method to return the appropriate value.</p> * * @return the value to return from <code>doAfterBody()</code> * * @return JspException if the value cannot be returned * * @throws JspException if an unexpected condition arises while * getting the value */ protected int getDoAfterBodyValue() throws JspException { return (SKIP_BODY); } // -------------------------------------------------------- BodyTag Methods /** * <p>Set the <code>bodyContent</code> for this tag handler. This method * is invoked by the JSP page implementation object at most once per * action invocation, before <code>doInitiBody()</code>. This method * will not be invoked for empty tags or for non-empty tags whose * <code>doStartTag()</code> method returns <code>SKIP_BODY</code> or * <code>EVAL_BODY_INCLUDE</code>.</p> * * @param bodyContent The new <code>BodyContent</code> for this tag */ @Override public void setBodyContent(BodyContent bodyContent) { this.bodyContent = bodyContent; } /** * <p>Get the <code>JspWriter</code> from our <code>BodyContent</code>. * </p> * * @return the writer */ public JspWriter getPreviousOut() { return (this.bodyContent.getEnclosingWriter()); } public BodyContent getBodyContent() { return (this.bodyContent); } /** * <p>Prepare for evaluation of the body. This method is invoked by the * JSP page implementation object after <code>setBodyContent()</code> * and before the first time the body is to be evaluated. This method * will not be invoked for empty tags or for non-empty tags whose * <code>doStartTag()</code> method returns <code>SKIP_BODY</code> * or <code>EVAL_BODY_INCLUDE</code>.</p> * * @throws JspException if an error is encountered */ @Override public void doInitBody() throws JspException { // Default implementation does nothing } /** * * <p>Perform any processing necessary to handle the content * implications of CASE 4 in the class description.</p> * * <p>Return result from {@link #getDoAfterBodyValue}</p> * @throws JspException if an error is encountered */ @Override public int doAfterBody() throws JspException { UIComponent verbatim; UIComponentClassicTagBase parentTag = _getParentUIComponentClassicTagBase(context.getAttributes()); // if we are the root tag, or if we are inside of a // rendersChildren==true component //noinspection ObjectEquality if (this == parentTag || (null != parentTag && parentTag.getComponentInstance().getRendersChildren())) { // stuff the template text or custom tag output into a // transient component if (null != (verbatim = this.createVerbatimComponentFromBodyContent())) { // EDGE CASE: // Consider CASE 4 where the component is provided via a // component binding in session or application scope. // The verbatim instance will already be present. If we // add again, the user will get duplicate component ID // errors. Check the JSP_CREATED_COMPONENT_IDS attribute. If it is not present, we // need to add the new verbatim child. If it is present, assume it is a // List and check its size. If the size of the list is equal to the // number of children currently in the component, replace the replace // the child of this component at the index derived as follows. If // indexOfChildInParent is 0, replace the child at the 0th index with // the new verbatim child. Otherwise, replace the child at the // (indexOfChildInParent - 1)th index with the new verbatim child. List createdIds = (List) component.getAttributes().get(JSP_CREATED_COMPONENT_IDS); if (createdIds != null) { int listIdx = component.getChildCount(); if (createdIds.size() == listIdx) { component.getChildren().set((listIdx - 1), verbatim); } else { component.getChildren().add(verbatim); } } else { component.getChildren().add(verbatim); } parentTag.addChild(verbatim); } } return (getDoAfterBodyValue()); } // ----------------------------------------------- Methods relating to Id /** * <p>Set the component identifier for our component. If the * argument begins with {@link * UIViewRoot#UNIQUE_ID_PREFIX} throw an * <code>IllegalArgumentException</code></p> * * @param id The new component identifier. This may not start with * {@link UIViewRoot#UNIQUE_ID_PREFIX}. * * @throws IllegalArgumentException if the argument is * non-<code>null</code> and starts with {@link * UIViewRoot#UNIQUE_ID_PREFIX}. */ @Override public void setId(String id) { if (null != id && id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) { throw new IllegalArgumentException(); } this.id = id; } /** * <p>Return the <code>id</code> value assigned by the page author.</p> * * @return the id of this tag */ protected String getId() { return (id); } /** * <p>If this method has been called before on this tag's useful * lifetime (before {@link #release} was called), return the * previously returned value. Otherwise, if {@link #getJspId} * returns non-<code>null</code>, prepend {@link #UNIQUE_ID_PREFIX} * to the <code>jspId</code> and return the result.</p> * * @return the value as specified above */ protected String getFacesJspId() { if (null == facesJspId) { if (null != jspId) { facesJspId = UNIQUE_ID_PREFIX + jspId; // if this tag happens to be nested within <c:forEach>, // jspId will be the same for each iteration. So it is // transformed into a unique "id" by appending a counter which // gets stored in request scope with jspId as the key for use // during the next iteration. if (isDuplicateId(facesJspId)) { facesJspId = generateIncrementedId(facesJspId); } } else { // jspId will be null if we're running in a container // that doesn't support JspIdConsumer facesJspId = getFacesContext().getViewRoot().createUniqueId(); } } return facesJspId; } /** * Returns true if a component already exists with the same * <code>id</code>. This will be the case if this tag is * nested within <code><c:forEach></code> tag or any other JSTL loop tag * or if the page has components with the same <code>Id</code>. * * @param componentId <code>id</code> to be looked up for possible * duplication. * @return true if this nested with <code>facesJspId</code> is duplicate. */ private boolean isDuplicateId(String componentId) { boolean result = false; if (parentTag != null) { if (parentTag.isNestedInIterator) { return true; } List childComponents = parentTag.createdComponents; // PENDING: Need to analyze the impact of this look up on pages // with several levels of nesting. if (childComponents != null) { result = childComponents.contains(componentId); if (result && (!isNestedInIterator)) { return true; } } } return result; } /* * Appends a counter to the passed in <code>id</code> and stores the * <code>id</code> and counter information in request scope. * * @return String <code>id</code> with a counter appended to it. */ private String generateIncrementedId (String componentId) { Integer serialNum = (Integer) context.getAttributes().get(componentId); if (null == serialNum) { serialNum = 1; } else { serialNum = serialNum.intValue() + 1; } context.getAttributes().put(componentId, serialNum); componentId = componentId + UNIQUE_ID_PREFIX + serialNum.intValue(); return componentId; } /** * Returns the <code>List</code> of {@link UIComponent} ids created or * located by nested {@link UIComponentTag}s while processing the current * request. * * @return the created components */ protected List<String> getCreatedComponents() { return createdComponents; } /** * <p>Create the component identifier to be used for this component.</p> */ private String createId(FacesContext context) throws JspException { if (this.id == null) { return getFacesJspId(); } else { // if this tag happens to be nested within <c:forEach>, jspId // will be the same for each iteration. So it is // transformed into a unique "id" by appending a counter which gets // stored in request scope with jspId as the key for use during next // iteration. if (isDuplicateId(this.id)) { if (!isSpecifiedIdUnique(this.id)) { if (isNestedInIterator) { this.id = generateIncrementedId(this.id); } else { StringWriter writer = new StringWriter(128); printTree(context.getViewRoot(), this.id, writer, 0); String msg = "Component ID '" + this.id + "' has already been used" + " in the view.\n" + "See below for the view up to the point of" + " the detected error.\n" + writer.toString(); throw new JspException(msg); } } } return (this.id); } } /** * @param id the component ID * @return <code>true</code> if this ID is unique within the closest naming * container, otherwise <code>false</code> */ private boolean isSpecifiedIdUnique(String id) { UIComponentClassicTagBase containerTag = getParentNamingContainerTag(); UIComponent c = containerTag.component.findComponent(id); if (c == null) { return true; } else { UIComponent parent = c.getParent(); if (parent.equals(this.parentTag.component)) { // the component we found has the same parent, If we find // a sibling with the same ID, return true so that the // id is incremented, otherwise, return false. List<String> created = this.parentTag.createdComponents; return !(created != null && created.contains(id)); } else { return false; } } } /** * @return the parent tag that represents the closest NamingContainer * component. */ private UIComponentClassicTagBase getParentNamingContainerTag() { if (this.parentTag == null) { return this; } UIComponentClassicTagBase parent = this.parentTag; while (parent != null) { if (parent.component instanceof NamingContainer || parent.parentTag == null && parent.component instanceof UIViewRoot) { return parent; } parent = parent.parentTag; } return null; } // ------------------------------------------------ JspIdConsumer Methods /** * <p>Defined on {@link JspIdConsumer}. This method is called by * the container before {@link #doStartTag}. The argument is * guaranteed to be unique within the page.</p> * * <p>IMPLEMENTATION NOTE: This method will detect where we * are in an include and assign a unique ID for each include * in a particular 'logical page'. This allows us to avoid * possible duplicate ID situations for included pages that * have components without explicit IDs.</p> * * @param id the container generated id for this tag, guaranteed to * be unique within the page. */ @Override public void setJspId(String id) { // reset JSP ID here instead of release as we may need // to check the ID after the tag has been used this.jspId = null; Integer pcId = (Integer) pageContext.getAttribute(JAVAX_FACES_PAGECONTEXT_MARKER, PageContext.PAGE_SCOPE); if (pcId == null) { if (null == context) { context = FacesContext.getCurrentInstance(); } AtomicInteger aInt = (AtomicInteger) context.getAttributes().get(JAVAX_FACES_PAGECONTEXT_COUNTER); if (aInt == null) { aInt = new AtomicInteger(); context.getAttributes().put(JAVAX_FACES_PAGECONTEXT_COUNTER, aInt); } pcId = aInt.incrementAndGet(); pageContext.setAttribute(JAVAX_FACES_PAGECONTEXT_MARKER, pcId); } if (pcId.intValue() > 1) { StringBuilder builder = new StringBuilder(id.length() + 3); builder.append(id).append("pc").append(pcId); jspId = builder.toString(); } else { jspId = id; } facesJspId = null; updatePreviousJspIdAndIteratorStatus(jspId); } /** * <p>Called from {@link #setJspId} to update the value saved for * the previous call to {@link #setJspId} for this component <b>on * this request</b>. If this method is presented with the same * argument <code>id</code> for the same tag instance more than once * on the same request, then we know that the tag instance lies * inside an iterator tag, such as <code>c:forEach</code>. If so, * we set the <code>isNestedInIterator</code> ivar to * <code>true</code> otherwise, we set it to <code>false</code>.</p> * * <p>The implementation for this method stores a Map from tag * instance to id String as a request scoped attribute. This map * contains the value used as the previousJspId and compared with * the argument <code>id</code>. * * @param id the id to be compared with the previous id, if any, for * this tag instance on this request. */ private void updatePreviousJspIdAndIteratorStatus(String id) { Set<String> previousJspIdSet = TypedCollections.dynamicallyCastSet((Set) pageContext.getAttribute(PREVIOUS_JSP_ID_SET, PageContext.PAGE_SCOPE), String.class); if (null == previousJspIdSet) { previousJspIdSet = new HashSet<>(); //noinspection CollectionWithoutInitialCapacity pageContext.setAttribute(PREVIOUS_JSP_ID_SET, previousJspIdSet, PageContext.PAGE_SCOPE); } // detect the iterator case, since add will return true if the collection already // contains the id if (previousJspIdSet.add(id)) { // id wasn't in Set, so we aren't nested yet isNestedInIterator = false; } else { // the Set didn't change, so we are nested if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Id " + id + " is nested within an iterating tag."); } isNestedInIterator = true; } } public String getJspId() { return jspId; } // ------------------------------------------------------- Abstract methods /** * <p>Override properties and attributes of the specified component, * if the corresponding properties of this tag handler instance were * explicitly set. This method must be called <strong>ONLY</strong> * if the specified {@link UIComponent} was in fact created during * the execution of this tag handler instance, and this call will occur * <strong>BEFORE</strong> the {@link UIComponent} is added to * the view.</p> * * <p>Tag subclasses that want to support additional set properties * must ensure that the base class <code>setProperties()</code> * method is still called. A typical implementation that supports * extra properties <code>foo</code> and <code>bar</code> would look * something like this:</p> * <pre> * protected void setProperties(UIComponent component) { * super.setProperties(component); * if (foo != null) { * component.setAttribute("foo", foo); * } * if (bar != null) { * component.setAttribute("bar", bar); * } * } * </pre> * * <p>The default implementation overrides the following properties:</p> * <ul> * <li><code>rendered</code> - Set if a value for the * <code>rendered</code> property is specified for * this tag handler instance.</li> * <li><code>rendererType</code> - Set if the <code>getRendererType()</code> * method returns a non-null value.</li> * </ul> * * @param component {@link UIComponent} whose properties are to be * overridden */ protected abstract void setProperties(UIComponent component); /** * <p>Create and return a new child component of the type returned * by calling <code>getComponentType()</code>. If this {@link * UIComponentTag} has a non-null <code>binding</code> attribute, * this is done by call {@link Application#createComponent} with the * expression created for the <code>binding</code> attribute, and * the expression will be stored on the component. Otherwise, * {@link Application#createComponent} is called with only the * component type. Finally, initialize the components id and other * properties. </p> * @param context {@link FacesContext} for the current request * @param newId id of the component * * @return the created component * * @throws JspException if the component cannot be created */ protected abstract UIComponent createComponent(FacesContext context, String newId) throws JspException; /** * <p>Return <code>true</code> if this component has a * non-<code>null</code> binding attribute. This method is * necessary to allow subclasses that expose the * <code>binding</code> property as an Faces 1.1 style EL property * as well as subclasses that expose it as an EL API property.</p> * * @return whether or not this component has a binding attribute */ protected abstract boolean hasBinding(); // --------------------------------------------------------- Properties /** * <p>Return the {@link UIComponent} instance that is associated with * this tag instance. This method is designed to be used by tags nested * within this tag, and only returns useful results between the * execution of <code>doStartTag()</code> and <code>doEndTag()</code> * on this tag instance.</p> */ @Override public UIComponent getComponentInstance() { return (this.component); } /** * <p>Return <code>true</code> if we dynamically created a new component * instance during execution of this tag. This method is designed to be * used by tags nested within this tag, and only returns useful results * between the execution of <code>doStartTag()</code> and * <code>doEndTag()</code> on this tag instance.</p> */ @Override public boolean getCreated() { // NOPMD return (this.created); } private Map getNamingContainerChildIds() { return (this.namingContainerChildIds); } @Override protected FacesContext getFacesContext() { if (context == null) { if (null == (context = (FacesContext) pageContext.getAttribute(CURRENT_FACES_CONTEXT))) { context = FacesContext.getCurrentInstance(); if (context == null) { // PENDING - i18n throw new RuntimeException("Cannot find FacesContext"); } // store the current FacesContext for use by other // UIComponentTags in the same page pageContext.setAttribute(CURRENT_FACES_CONTEXT, context); } } return (context); } /** * <p>Return the facet name that we should be stored under, if any; * otherwise, return null (indicating that we will be a child component). * </p> * * @return the name of the facet */ protected String getFacetName() { Tag parent = getParent(); if (parent instanceof FacetTag) { return (((FacetTag) parent).getName()); } else { return (null); } } private static FacesContext getFacesContext(PageContext pageContext) { FacesContext context = (FacesContext) pageContext.getAttribute(CURRENT_FACES_CONTEXT); if (context == null) { context = FacesContext.getCurrentInstance(); if (context == null) { throw new RuntimeException("Cannot find FacesContext"); } else { pageContext.setAttribute(CURRENT_FACES_CONTEXT, context); } } return (context); } private static void printTree(UIComponent root, String duplicateId, Writer out, int curDepth) { if (null == root) { return; } if (duplicateId.equals(root.getId())) { indentPrintln(out, "+id: " + root.getId() + " <===============", curDepth); } else { indentPrintln(out, "+id: " + root.getId(), curDepth); } //noinspection ObjectToString indentPrintln(out, " type: " + root.toString(), curDepth); curDepth++; // print all the facets of this component for (UIComponent uiComponent : root.getFacets().values()) { printTree(uiComponent, duplicateId, out, curDepth); } // print all the children of this component for (UIComponent uiComponent : root.getChildren()) { printTree(uiComponent, duplicateId, out, curDepth); } } private static void indentPrintln(Writer out, String str, int curDepth) { // handle indentation try { for (int i = 0; i < curDepth; i++) { out.write(" "); } out.write(str + '\n'); } catch (IOException ex) { // ignore } } }