/*
* 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 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>
*
* <code><pre>
<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>
* </pre></code>
*
* <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>
*
* <code><pre>
<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>
* </pre></code>
*
*/
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
*/
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
*/
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>
*/
public void
setPageContext(
PageContext pageContext) {
this.
pageContext =
pageContext;
}
/**
* <p>Return the <code>Tag</code> that is the parent of this instance.</p>
*/
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>
*/
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>
*/
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
*/
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.
//
protected int
getIndexOfNextChildTag() {
if (
createdComponents != null) {
return (
createdComponents.
size());
} else {
return (0);
}
}
protected void
addChild(
UIComponent child) {
if (
createdComponents == null) {
createdComponents = new
ArrayList<
String>(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);
}
protected void
addFacet(
String name) {
if (
createdFacets == null) {
//noinspection CollectionWithoutInitialCapacity
createdFacets = new
ArrayList<
String>(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<
UIComponentClassicTagBase>();
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>
*/
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>
*
*/
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>
*/
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>
*/
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
*/
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<
String,
Map<
String,
UIComponentTagBase>>();
}
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
*/
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>
*/
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>
*/
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
*/
public void
setBodyContent(
BodyContent bodyContent) {
this.
bodyContent =
bodyContent;
}
/**
* <p>Get the <code>JspWriter</code> from our <code>BodyContent</code>.
* </p>
*/
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
*/
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
*/
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}.
*/
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>
*/
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>
*/
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.</p>
*/
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.
*/
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<
String>();
//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
*/
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>
*/
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>
*/
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>
*/
public boolean
getCreated() {
return (this.
created);
}
private
Map getNamingContainerChildIds() {
return (this.
namingContainerChildIds);
}
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>
*/
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
}
}
}