blob: 9c431e93100b89a292a544df54429b076d3672d6 [file] [log] [blame]
/*
* 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;
import java.util.ArrayDeque;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.IMarkupFragment;
import org.apache.wicket.markup.MarkupElement;
/**
* Context for component dequeueing. Keeps track of markup position and container stack.
*
* @author igor
*
*/
public final class DequeueContext
{
private final IMarkupFragment markup;
private int index;
private ComponentTag next;
private ArrayDeque<ComponentTag> tags = new ArrayDeque<>();
private final boolean skipFirst;
private ComponentTag first;
private ArrayDeque<MarkupContainer> containers = new ArrayDeque<>();
/** A bookmark for the DequeueContext stack */
public static final class Bookmark
{
private final int index;
private final ComponentTag next;
private final ArrayDeque<ComponentTag> tags;
private final ArrayDeque<MarkupContainer> containers;
private Bookmark(DequeueContext parser)
{
this.index = parser.index;
this.next = parser.next;
this.tags = new ArrayDeque<>(parser.tags);
this.containers = new ArrayDeque<>(parser.containers);
}
private void restore(DequeueContext parser)
{
parser.index = index;
parser.next = next;
parser.tags = new ArrayDeque<>(tags);
parser.containers = new ArrayDeque<>(containers);
}
}
public DequeueContext(IMarkupFragment markup, MarkupContainer root, boolean skipFirst)
{
this.markup = markup;
this.skipFirst = skipFirst;
this.containers.push(root);
this.next = nextTag();
}
/**
* Saves the state of the context into a bookmark which can later be used to restore it.
*/
public Bookmark save()
{
return new Bookmark(this);
}
/**
* Restores the state of the context from the bookmark
*
* @param bookmark
*/
public void restore(Bookmark bookmark)
{
bookmark.restore(this);
}
/**
* Peeks markup tag that would be retrieved by call to {@link #takeTag()}
*
* @return
*/
public ComponentTag peekTag()
{
return next;
}
/**
* Retrieves the next markup tag
*
* @return
*/
public ComponentTag takeTag()
{
ComponentTag taken = next;
if (taken == null)
{
return null;
}
if (taken.isOpen() && !taken.hasNoCloseTag())
{
tags.push(taken);
}
else if (tags.size() > 0 && taken.closes(tags.peek()))
{
tags.pop();
}
next = nextTag();
return taken;
}
/**
* Skips to the closing tag of the tag retrieved from last call to {@link #takeTag()}
*/
public void skipToCloseTag()
{
while (!next.closes(tags.peek()))
{
next = nextTag();
}
}
private ComponentTag nextTag()
{
if (skipFirst && first == null)
{
for (; index < markup.size(); index++)
{
MarkupElement element = markup.get(index);
if (element instanceof ComponentTag)
{
first = (ComponentTag)element;
index++;
break;
}
}
}
for (; index < markup.size(); index++)
{
MarkupElement element = markup.get(index);
if (element instanceof ComponentTag)
{
ComponentTag tag = (ComponentTag)element;
if (tag.isOpen() || tag.isOpenClose())
{
DequeueTagAction action = canDequeueTag(tag);
switch (action)
{
case IGNORE :
continue;
case DEQUEUE :
index++;
return tag;
case SKIP : // skip to close tag
boolean found = false;
for (; index < markup.size(); index++)
{
if ((markup.get(index) instanceof ComponentTag)
&& markup.get(index).closes(tag))
{
found = true;
break;
}
}
if (!found)
{
throw new IllegalStateException(String.format(
"Could not find close tag for tag '%s' in markup: %s ", tag,
markup));
}
}
}
else
{
// closed tag
ComponentTag open = tag.isClose() ? tag.getOpenTag() : tag;
if (skipFirst && first != null && open == first)
{
continue;
}
switch (canDequeueTag(open))
{
case DEQUEUE :
index++;
return tag;
case IGNORE :
continue;
case SKIP :
throw new IllegalStateException(String.format(
"Should not see closed tag of skipped open tag '%s' in markup:%s",
tag, markup));
}
}
}
}
return null;
}
private DequeueTagAction canDequeueTag(ComponentTag open)
{
if (containers.size() < 1)
{
// TODO queueing message: called too early
throw new IllegalStateException();
}
DequeueTagAction action;
for (MarkupContainer container : containers)
{
action = container.canDequeueTag(open);
if (action != null)
{
return action;
}
}
return DequeueTagAction.IGNORE;
}
/**
* Checks if the tag returned by {@link #peekTag()} is either open or open-close.
*
* @return
*/
public boolean isAtOpenOrOpenCloseTag()
{
ComponentTag tag = peekTag();
return tag != null && (tag.isOpen() || tag.isOpenClose());
}
/**
* Retrieves the container on the top of the containers stack
*
* @return
*/
public MarkupContainer peekContainer()
{
return containers.peek();
}
/**
* Pushes a container onto the container stack
*
* @param container
*/
public void pushContainer(MarkupContainer container)
{
containers.push(container);
}
/**
* Pops a container from the container stack
*
* @return
*/
public MarkupContainer popContainer()
{
return containers.pop();
}
/**
* Searches the container stack for a component that can be dequeude
*
* @param tag
* @return
*/
public Component findComponentToDequeue(ComponentTag tag)
{
for (MarkupContainer container : containers)
{
Component child = container.findComponentToDequeue(tag);
if (child != null)
{
return child;
}
}
return null;
}
}