/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.wicket.markup;
import java.util.Iterator;
import org.apache.wicket.markup.parser.filter.HtmlHandler;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.AppendingStringBuffer;
/**
* Represents a portion of a markup file, but always spans a complete tag. E.g.
*
* <pre>
* open-body-close: <span>body</span>
* open-close: <span/>
* open-no-close: <input ...>body
* </pre>
*
* @see Markup
* @see MarkupElement
*
* @author Juergen Donnerstag
*/
public class MarkupFragment extends AbstractMarkupFragment
{
/** The parent markup. Must not be null. */
private final IMarkupFragment markup;
/** The index at which the fragment starts, relative to the parent markup */
private final int startIndex;
/** The size of the fragment (usually from open to close tag) */
private final int size;
/**
* Construct.
*
* @param markup
* The parent markup. May not be null.
* @param startIndex
* The start index of the child markup
* @throws IndexOutOfBoundsException
* if the index is out of range (<tt>index < 0 || index >= size()</tt>)
*/
public MarkupFragment(final IMarkupFragment markup, final int startIndex)
{
Args.notNull(markup, "markup");
if (startIndex < 0)
{
throw new IllegalArgumentException("Parameter 'startIndex' must not be < 0");
}
// cache the value for better performance
int markupSize = markup.size();
if (startIndex >= markupSize)
{
throw new IllegalArgumentException(
"Parameter 'startIndex' must not be >= markup.size()");
}
this.markup = markup;
this.startIndex = startIndex;
// Make sure we are at an open tag
MarkupElement startElem = markup.get(startIndex);
if ((startElem instanceof ComponentTag) == false)
{
throw new IllegalArgumentException(
"Parameter 'startIndex' does not point to a Wicket open tag");
}
// Determine the size. Find the close tag
int endIndex;
ComponentTag startTag = (ComponentTag)startElem;
if (startTag.isOpenClose())
{
endIndex = startIndex;
}
else if (startTag.hasNoCloseTag())
{
if (HtmlHandler.requiresCloseTag(startTag.getName()) == false)
{
// set endIndex to a "good" value
endIndex = startIndex;
}
else
{
// set endIndex to a value which will indicate an error
endIndex = markupSize;
}
}
else
{
for (endIndex = startIndex + 1; endIndex < markupSize; endIndex++)
{
MarkupElement elem = markup.get(endIndex);
if (elem instanceof ComponentTag)
{
ComponentTag tag = (ComponentTag)elem;
if (tag.closes(startTag))
{
break;
}
}
}
}
if (endIndex >= markupSize)
{
throw new MarkupException("Unable to find close tag for: '" + startTag.toString() +
"' in " + getRootMarkup().getMarkupResourceStream().toString());
}
size = endIndex - startIndex + 1;
}
@Override
public final MarkupElement get(final int index)
{
if ((index < 0) || (index > size))
{
throw new IndexOutOfBoundsException("Parameter 'index' is out of range: 0 <= " + index +
" <= " + size);
}
// Ask the parent markup
return markup.get(startIndex + index);
}
@Override
public final IMarkupFragment find(final String id)
{
if (size < 2)
{
return null;
}
return find(id, 1);
}
@Override
public final MarkupResourceStream getMarkupResourceStream()
{
return markup.getMarkupResourceStream();
}
@Override
public final int size()
{
return size;
}
/**
* @return The parent markup. Null if that is a markup file.
*/
private IMarkupFragment getParentMarkup()
{
return markup;
}
/**
* @return The Markup representing the underlying markup file with all its content
*/
public final Markup getRootMarkup()
{
IMarkupFragment markup = getParentMarkup();
while ((markup != null) && !(markup instanceof Markup))
{
markup = ((MarkupFragment)markup).getParentMarkup();
}
return (Markup)markup;
}
@Override
public String toString(boolean markupOnly)
{
final AppendingStringBuffer buf = new AppendingStringBuffer(400);
if (markupOnly == false)
{
buf.append(getRootMarkup().getMarkupResourceStream().toString());
buf.append('\n');
}
for (int i = 0; i < size(); i++)
{
buf.append(get(i));
}
return buf.toString();
}
@Override
public Iterator<MarkupElement> iterator()
{
return new Iterator<MarkupElement>() {
int index = 0;
@Override
public boolean hasNext() {
return index < size;
}
@Override
public MarkupElement next() {
return get(index++);
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove");
}
};
}
}