/*
* 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.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.wicket.Component;
import org.apache.wicket.core.util.lang.WicketObjects;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Bytes;
import org.apache.wicket.util.resource.IFixedLocationResourceStream;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.time.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An IResourceStream implementation with specific extensions for markup resource streams.
*
* @author Juergen Donnerstag
*/
public class MarkupResourceStream implements IResourceStream, IFixedLocationResourceStream
{
private static final long serialVersionUID = 1846489965076612828L;
private static final Logger log = LoggerFactory.getLogger(MarkupResourceStream.class);
/** */
public static final String WICKET_XHTML_DTD = "http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd";
private static final Pattern DOCTYPE_REGEX = Pattern.compile("!DOCTYPE\\s+(.*)\\s*");
/** The associated markup resource stream */
private final IResourceStream resourceStream;
/** Container info like Class, locale and style which were used to locate the resource */
private final transient ContainerInfo containerInfo;
/**
* The actual component class the markup is directly associated with. It might be super class of
* the component class
*/
private final String markupClassName;
/** The key used to cache the markup resource stream */
private String cacheKey;
/** In case of the inherited markup, this is the base markup */
private transient Markup baseMarkup;
/** The encoding as found in <?xml ... encoding="" ?>. {@code null}, otherwise */
private String encoding;
/** Wicket namespace: see WICKET_XHTML_DTD */
private String wicketNamespace;
/** == wicket namespace name + ":id" */
private String wicketId;
/** HTML5 http://www.w3.org/TR/html5-diff/#doctype */
private String doctype;
/**
* Construct.
*
* @param resourceStream
*/
public MarkupResourceStream(final IResourceStream resourceStream)
{
this(resourceStream, null, null);
}
/**
* Construct.
*
* @param resourceStream
* @param containerInfo
* @param markupClass
*/
public MarkupResourceStream(final IResourceStream resourceStream,
final ContainerInfo containerInfo, final Class<?> markupClass)
{
this.resourceStream = Args.notNull(resourceStream, "resourceStream");
this.containerInfo = containerInfo;
markupClassName = markupClass == null ? null : markupClass.getName();
setWicketNamespace(MarkupParser.WICKET);
}
@Override
public String locationAsString()
{
if (resourceStream instanceof IFixedLocationResourceStream)
{
return ((IFixedLocationResourceStream)resourceStream).locationAsString();
}
return null;
}
@Override
public void close() throws IOException
{
resourceStream.close();
}
@Override
public String getContentType()
{
return resourceStream.getContentType();
}
@Override
public InputStream getInputStream() throws ResourceStreamNotFoundException
{
return resourceStream.getInputStream();
}
@Override
public Locale getLocale()
{
return resourceStream.getLocale();
}
@Override
public Time lastModifiedTime()
{
return resourceStream.lastModifiedTime();
}
@Override
public Bytes length()
{
return resourceStream.length();
}
@Override
public void setLocale(Locale locale)
{
resourceStream.setLocale(locale);
}
/**
* Get the actual component class the markup is directly associated with. Note: it not
* necessarily must be the container class.
*
* @return The directly associated class
*/
public Class<? extends Component> getMarkupClass()
{
if (markupClassName == null)
{
throw new MarkupException("no associated markup class");
}
return WicketObjects.resolveClass(markupClassName);
}
/**
* Get the container info associated with the markup
*
* @return ContainerInfo
*/
public ContainerInfo getContainerInfo()
{
return containerInfo;
}
/**
* Gets cacheKey.
*
* @return cacheKey
*/
public final String getCacheKey()
{
return cacheKey;
}
/**
* Set the cache key
*
* @param cacheKey
*/
public final void setCacheKey(final String cacheKey)
{
this.cacheKey = cacheKey;
}
/**
* Gets the resource that contains this markup
*
* @return The resource where this markup came from
*/
public IResourceStream getResource()
{
return resourceStream;
}
/**
* Gets the markup encoding. A markup encoding may be specified in a markup file with an XML
* encoding specifier of the form <?xml ... encoding="..." ?>.
*
* @return Encoding, or null if not found.
*/
public String getEncoding()
{
return encoding;
}
/**
* Get the wicket namespace valid for this specific markup
*
* @return wicket namespace
*/
public String getWicketNamespace()
{
return wicketNamespace;
}
/**
*
* @return usually it is "wicket:id"
*/
final public String getWicketId()
{
return wicketId;
}
/**
* Sets encoding.
*
* @param encoding
* encoding
*/
final void setEncoding(final String encoding)
{
this.encoding = encoding;
}
/**
* Sets wicketNamespace.
*
* @param wicketNamespace
* wicketNamespace
*/
public final void setWicketNamespace(final String wicketNamespace)
{
this.wicketNamespace = wicketNamespace;
wicketId = (wicketNamespace + ":id").intern();
if (!MarkupParser.WICKET.equals(wicketNamespace) && log.isDebugEnabled())
{
log.debug("You are using a non-standard namespace name: '{}'", wicketNamespace);
}
}
/**
* Get the resource stream containing the base markup (markup inheritance)
*
* @return baseMarkupResource Null, if not base markup
*/
public MarkupResourceStream getBaseMarkupResourceStream()
{
if (baseMarkup == null)
{
return null;
}
return baseMarkup.getMarkupResourceStream();
}
/**
* In case of markup inheritance, the base markup.
*
* @param baseMarkup
* The base markup
*/
public void setBaseMarkup(Markup baseMarkup)
{
this.baseMarkup = baseMarkup;
}
/**
* In case of markup inheritance, the base markup resource.
*
* @return The base markup
*/
public Markup getBaseMarkup()
{
return baseMarkup;
}
@Override
public String getStyle()
{
return resourceStream.getStyle();
}
@Override
public String getVariation()
{
return resourceStream.getVariation();
}
@Override
public void setStyle(String style)
{
resourceStream.setStyle(style);
}
@Override
public void setVariation(String variation)
{
resourceStream.setVariation(variation);
}
@Override
public String toString()
{
if (resourceStream != null)
{
return resourceStream.toString();
}
else
{
return "(unknown resource)";
}
}
/**
* Gets doctype.
*
* @return The doctype excluding 'DOCTYPE'
*/
public final String getDoctype()
{
if (doctype == null)
{
MarkupResourceStream baseMarkupResourceStream = getBaseMarkupResourceStream();
if (baseMarkupResourceStream != null)
{
doctype = baseMarkupResourceStream.getDoctype();
}
}
return doctype;
}
/**
* Sets doctype.
*
* @param doctype
* doctype
*/
public final void setDoctype(final CharSequence doctype)
{
if (Strings.isEmpty(doctype) == false)
{
String doc = doctype.toString().replaceAll("[\n\r]+", "");
doc = doc.replaceAll("\\s+", " ");
Matcher matcher = DOCTYPE_REGEX.matcher(doc);
if (matcher.matches() == false)
{
throw new MarkupException("Invalid DOCTYPE: '" + doctype + "'");
}
this.doctype = matcher.group(1).trim();
}
}
/**
* @see <a href="http://www.w3.org/TR/html5-diff/#doctype">DOCTYPE</a>
* @return True, if doctype == <!DOCTYPE html>
*/
public boolean isHtml5()
{
return "html".equalsIgnoreCase(getDoctype());
}
}