/* Copyright 2012 Ben Gunter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sourceforge.stripes.tag.layout;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.jsp.PageContext;
import net.sourceforge.stripes.exception.StripesJspException;
/**
* Uniquely identifies a {@link LayoutRenderTag} within a page. Within a single page, any number of
* render tags can be accessible via the same "path," where a path consists of zero or more
* component tags that are parents of the render tag. This class helps to distinguish between
* multiple render tags with the same component path by assigning sequential indexes to them.
*
* @author Ben Gunter
* @since Stripes 1.5.7
*/
public class LayoutRenderTagPath {
private List<String> componentPath;
private int index;
/** Construct a new instance to identify the specified tag. */
public LayoutRenderTagPath(LayoutRenderTag tag) {
this.componentPath = calculateComponentPath(tag);
this.index = incrementIndex(tag.getPageContext());
}
/**
* Calculate the path to a render tag. The path is a list of names of components that must
* execute, in order, so that the specified render tag can execute.
*
* @param tag The render tag.
* @return A list of component names or null if the render tag is not a child of a component.
*/
protected List<String> calculateComponentPath(LayoutRenderTag tag) {
LinkedList<String> path = null;
for (LayoutTag parent = tag.getLayoutParent(); parent instanceof LayoutComponentTag;) {
if (path == null)
path = new LinkedList<String>();
path.addFirst(((LayoutComponentTag) parent).getName());
parent = parent.getLayoutParent();
parent = parent instanceof LayoutRenderTag ? parent.getLayoutParent() : null;
}
return path;
}
/** Get the next index for this path from the specified page context. */
protected int incrementIndex(PageContext pageContext) {
String key = getClass().getName() + "#" + toStringWithoutIndex();
Integer index = (Integer) pageContext.getAttribute(key);
if (index == null)
index = 0;
else
++index;
pageContext.setAttribute(key, index);
return index;
}
/** Get the names of the {@link LayoutComponentTag}s that are parent tags of the render tag. */
public List<String> getComponentPath() {
return componentPath;
}
/** Get the index (zero-based) of the combined render page and component path within the page. */
public int getIndex() {
return index;
}
/**
* True if the specified tag is a component that must execute so that the current component tag
* can execute. That is, this tag is a parent of the current component.
*
* @param tag The tag to check to see if it is part of this path.
*/
public boolean isPathComponent(LayoutComponentTag tag) throws StripesJspException {
List<String> path = getComponentPath();
return path == null ? false : isPathComponent(tag, path.iterator());
}
/**
* Recursive method called from {@link #isPathComponent(LayoutComponentTag)} that returns true
* if the specified tag's name is present in the component path iterator at the same position
* where this tag occurs in the render/component tag tree. For example, if the path iterator
* contains the component names {@code ["foo", "bar"]} then this method will return true if the
* tag's name is {@code "bar"} and it is a child of a render tag that is a child of a component
* tag whose name is {@code "foo"}.
*
* @param tag The tag to check
* @param path The path to the check the tag against
*/
protected boolean isPathComponent(LayoutComponentTag tag, Iterator<String> path) {
LayoutTag parent = tag.getLayoutParent();
if (parent instanceof LayoutRenderTag) {
parent = parent.getLayoutParent();
if (!(parent instanceof LayoutComponentTag) || parent instanceof LayoutComponentTag
&& isPathComponent((LayoutComponentTag) parent, path) && path.hasNext()) {
return tag.getName().equals(path.next());
}
}
return false;
}
@Override
public boolean equals(Object obj) {
LayoutRenderTagPath that = (LayoutRenderTagPath) obj;
if (index != that.index)
return false;
if (componentPath == that.componentPath)
return true;
if (componentPath == null || that.componentPath == null)
return false;
return componentPath.equals(that.componentPath);
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
return toStringWithoutIndex() + '[' + index + ']';
}
/** Get a string representation of this instance without including the index. */
public String toStringWithoutIndex() {
if (componentPath == null)
return "";
StringBuilder s = new StringBuilder();
for (Iterator<String> it = componentPath.iterator(); it.hasNext();) {
s.append(it.next());
if (it.hasNext())
s.append('>');
}
return s.toString();
}
}