org.apache.wicket.markup.MergedMarkup Maven / Gradle / Ivy
Show all versions of org.ops4j.pax.wicket.service Show documentation
/*
* 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 org.apache.wicket.Page;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.parser.XmlTag;
import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Markup class which represents merged markup, as it is required for markup inheritance.
*
* The Markups are merged at load time. Deep markup hierarchies are supported. Multiple inheritance
* is not.
*
* The markup resource file, which is associated with the markup, will be the resource of the
* requested markup file. The base markup resources are not.
*
* Base Markup must have a <wicket:child/> tag at the position where the derived markup should
* be inserted. From the derived markup all tags in between <wicket:extend> and
* </wicket:extend> will be inserted.
*
* In addition, all <wicket:head> regions are copied as well. This allows to develop completely
* self-contained plug & play components including javascript etc.
*
* @author Juergen Donnerstag
*/
public class MergedMarkup extends Markup
{
private final static Logger log = LoggerFactory.getLogger(MergedMarkup.class);
/**
* Merge inherited and base markup.
*
* @param markup
* The inherited markup
* @param baseMarkup
* The base markup
* @param extendIndex
* Index where has been found
*/
public MergedMarkup(final Markup markup, final Markup baseMarkup, int extendIndex)
{
super(new MarkupResourceData());
// Copy settings from derived markup
getMarkupResourceData().setResource(markup.getMarkupResourceData().getResource());
getMarkupResourceData().setXmlDeclaration(
markup.getMarkupResourceData().getXmlDeclaration());
getMarkupResourceData().setEncoding(markup.getMarkupResourceData().getEncoding());
getMarkupResourceData().setWicketNamespace(
markup.getMarkupResourceData().getWicketNamespace());
getMarkupResourceData().setBaseMarkup(baseMarkup);
if (log.isDebugEnabled())
{
String derivedResource = Strings.afterLast(markup.getMarkupResourceData()
.getResource()
.toString(), '/');
String baseResource = Strings.afterLast(baseMarkup.getMarkupResourceData()
.getResource()
.toString(), '/');
log.debug("Merge markup: derived markup: " + derivedResource + "; base markup: " +
baseResource);
}
// Merge derived and base markup
merge(markup, baseMarkup, extendIndex);
// Initialize internals based on new markup
initialize();
if (log.isDebugEnabled())
{
log.debug("Merge markup: " + toString());
}
}
/**
* @see org.apache.wicket.markup.Markup#locationAsString()
*/
@Override
public String locationAsString()
{
/*
* Uses both resource locations so that if the child does not have a style and the parent
* does, the location is unique to this combination (or vice versa) SEE WICKET-1507 (Jeremy
* Thomerson)
*/
String l1 = getMarkupResourceData().getBaseMarkup().locationAsString();
String l2 = getMarkupResourceData().getResource().locationAsString();
if ((l1 == null) && (l2 == null))
{
return null;
}
return l1 + ":" + l2;
}
/**
* Merge inherited and base markup.
*
* @param markup
* The inherited markup
* @param baseMarkup
* The base markup
* @param extendIndex
* Index where has been found
*/
private void merge(final Markup markup, final Markup baseMarkup, int extendIndex)
{
// True if either or has been processed
boolean wicketHeadProcessed = false;
// True, if was found
boolean foundHeadTag = false;
// Add all elements from the base markup to the new list
// until is found. Convert
// into and add it as well.
WicketTag childTag = null;
int baseIndex = 0;
for (; baseIndex < baseMarkup.size(); baseIndex++)
{
MarkupElement element = baseMarkup.get(baseIndex);
if (element instanceof RawMarkup)
{
// Add the element to the merged list
addMarkupElement(element);
continue;
}
final ComponentTag tag = (ComponentTag)element;
// Make sure all tags of the base markup remember where they are
// from
if ((baseMarkup.getMarkupResourceData().getResource() != null) &&
(tag.getMarkupClass() == null))
{
tag.setMarkupClass(baseMarkup.getMarkupResourceData()
.getResource()
.getMarkupClass());
}
if (element instanceof WicketTag)
{
WicketTag wtag = (WicketTag)element;
// Found org.apache.wicket.child in base markup. In case of 3+
// level inheritance make sure the child tag is not from one of
// the deeper levels
if (wtag.isChildTag() &&
(tag.getMarkupClass() == baseMarkup.getMarkupResourceData()
.getResource()
.getMarkupClass()))
{
if (wtag.isOpenClose())
{
// => ...
childTag = wtag;
WicketTag childOpenTag = (WicketTag)wtag.mutable();
childOpenTag.getXmlTag().setType(XmlTag.OPEN);
childOpenTag.setMarkupClass(baseMarkup.getMarkupResourceData()
.getResource()
.getMarkupClass());
addMarkupElement(childOpenTag);
break;
}
else if (wtag.isOpen())
{
//
addMarkupElement(wtag);
break;
}
else
{
throw new WicketRuntimeException(
"Did not expect a tag in " + baseMarkup.toString());
}
}
// Process the head of the extended markup only once
if (wicketHeadProcessed == false)
{
// if in base markup and no
if (wtag.isClose() && wtag.isHeadTag() && (foundHeadTag == false))
{
wicketHeadProcessed = true;
// Add the current close tag
addMarkupElement(wtag);
// Add the body from the derived markup.
copyWicketHead(markup, extendIndex);
// Do not add the current tag. It has already been
// added.
continue;
}
// if or ... in base markup
if (wtag.isOpen() && wtag.isMajorWicketComponentTag())
{
wicketHeadProcessed = true;
// Add the body from the derived markup.
copyWicketHead(markup, extendIndex);
}
}
}
// Process the head of the extended markup only once
if (wicketHeadProcessed == false)
{
// Remember that we found in the base markup
if (tag.isOpen() && TagUtils.isHeadTag(tag))
{
foundHeadTag = true;
}
// if in base markup
if ((tag.isClose() && TagUtils.isHeadTag(tag)) ||
(tag.isOpen() && TagUtils.isBodyTag(tag)))
{
wicketHeadProcessed = true;
// Add the body from the derived markup.
copyWicketHead(markup, extendIndex);
}
}
// Add the element to the merged list
addMarkupElement(element);
}
if (baseIndex == baseMarkup.size())
{
throw new WicketRuntimeException("Expected to find in base markup: " +
baseMarkup.toString());
}
// Now append all elements from the derived markup starting with
// until to the list
for (; extendIndex < markup.size(); extendIndex++)
{
MarkupElement element = markup.get(extendIndex);
addMarkupElement(element);
if (element instanceof WicketTag)
{
WicketTag wtag = (WicketTag)element;
if (wtag.isExtendTag() && wtag.isClose())
{
break;
}
}
}
if (extendIndex == markup.size())
{
throw new WicketRuntimeException(
"Missing close tag in derived markup: " + markup.toString());
}
// If than skip the body and find
if (((ComponentTag)baseMarkup.get(baseIndex)).isOpen())
{
for (baseIndex++; baseIndex < baseMarkup.size(); baseIndex++)
{
MarkupElement element = baseMarkup.get(baseIndex);
if (element instanceof WicketTag)
{
WicketTag tag = (WicketTag)element;
if (tag.isChildTag() && tag.isClose())
{
// Ok, skipped the childs content
tag.setMarkupClass(baseMarkup.getMarkupResourceData()
.getResource()
.getMarkupClass());
addMarkupElement(tag);
break;
}
else
{
throw new WicketRuntimeException(
"Wicket tags like are not allowed in between and tags: " +
markup.toString());
}
}
else if (element instanceof ComponentTag)
{
throw new WicketRuntimeException(
"Wicket tags identified by wicket:id are not allowed in between and tags: " +
markup.toString());
}
}
// not found
if (baseIndex == baseMarkup.size())
{
throw new WicketRuntimeException(
"Expected to find in base markup: " + baseMarkup.toString());
}
}
else
{
// And now all remaining elements from the derived markup.
// But first add
WicketTag childCloseTag = (WicketTag)childTag.mutable();
childCloseTag.getXmlTag().setType(XmlTag.CLOSE);
childCloseTag.setMarkupClass(baseMarkup.getMarkupResourceData()
.getResource()
.getMarkupClass());
addMarkupElement(childCloseTag);
}
for (baseIndex++; baseIndex < baseMarkup.size(); baseIndex++)
{
MarkupElement element = baseMarkup.get(baseIndex);
addMarkupElement(element);
// Make sure all tags of the base markup remember where they are
// from
if ((element instanceof ComponentTag) &&
(baseMarkup.getMarkupResourceData().getResource() != null))
{
ComponentTag tag = (ComponentTag)element;
tag.setMarkupClass(baseMarkup.getMarkupResourceData()
.getResource()
.getMarkupClass());
}
}
// Automatically add if missing and required. On a Page
// it must enclose ALL of the tags.
// Note: HtmlHeaderSectionHandler does something similar, but because
// markup filters are not called for merged markup again, ...
if (Page.class.isAssignableFrom(markup.getMarkupResourceData()
.getResource()
.getMarkupClass()))
{
// Find the position inside the markup for first ,
// last and
int hasOpenWicketHead = -1;
int hasCloseWicketHead = -1;
int hasHead = -1;
for (int i = 0; i < size(); i++)
{
MarkupElement element = get(i);
if ((hasOpenWicketHead == -1) && (element instanceof WicketTag) &&
((WicketTag)element).isHeadTag())
{
hasOpenWicketHead = i;
}
else if ((element instanceof WicketTag) && ((WicketTag)element).isHeadTag() &&
((WicketTag)element).isClose())
{
hasCloseWicketHead = i;
}
else if ((hasHead == -1) && (element instanceof ComponentTag) &&
TagUtils.isHeadTag((ComponentTag)element))
{
hasHead = i;
}
else if ((hasHead != -1) && (hasOpenWicketHead != -1))
{
break;
}
}
// If a tag is missing, insert it automatically
if ((hasOpenWicketHead != -1) && (hasHead == -1))
{
final XmlTag headOpenTag = new XmlTag();
headOpenTag.setName("head");
headOpenTag.setType(XmlTag.OPEN);
final ComponentTag openTag = new ComponentTag(headOpenTag);
openTag.setId(HtmlHeaderSectionHandler.HEADER_ID);
openTag.setAutoComponentTag(true);
final XmlTag headCloseTag = new XmlTag();
headCloseTag.setName(headOpenTag.getName());
headCloseTag.setType(XmlTag.CLOSE);
final ComponentTag closeTag = new ComponentTag(headCloseTag);
closeTag.setOpenTag(openTag);
closeTag.setId(HtmlHeaderSectionHandler.HEADER_ID);
addMarkupElement(hasOpenWicketHead, openTag);
addMarkupElement(hasCloseWicketHead + 2, closeTag);
}
}
}
/**
* Append the wicket:head regions from the extended markup to the current markup
*
* @param markup
* @param extendIndex
*/
private void copyWicketHead(final Markup markup, int extendIndex)
{
boolean copy = false;
for (int i = 0; i < extendIndex; i++)
{
MarkupElement elem = markup.get(i);
if (elem instanceof WicketTag)
{
WicketTag etag = (WicketTag)elem;
if (etag.isHeadTag())
{
if (etag.isOpen())
{
copy = true;
}
else
{
addMarkupElement(elem);
break;
}
}
}
if (copy == true)
{
addMarkupElement(elem);
}
}
}
}