![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.juneau.html.HtmlSerializerSession Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.juneau.html;
import static org.apache.juneau.common.internal.IOUtils.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static org.apache.juneau.xml.XmlSerializerSession.ContentResult.*;
import java.io.*;
import java.lang.reflect.*;
import java.nio.charset.*;
import java.util.*;
import java.util.function.*;
import java.util.regex.*;
import org.apache.juneau.*;
import org.apache.juneau.html.annotation.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
import org.apache.juneau.swap.*;
import org.apache.juneau.xml.*;
import org.apache.juneau.xml.annotation.*;
/**
* Session object that lives for the duration of a single use of {@link HtmlSerializer}.
*
* Notes:
* - This class is not thread safe and is typically discarded after one use.
*
*
* See Also:
* - HTML Details
*
*/
public class HtmlSerializerSession extends XmlSerializerSession {
//-----------------------------------------------------------------------------------------------------------------
// Static
//-----------------------------------------------------------------------------------------------------------------
/**
* Creates a new builder for this object.
*
* @param ctx The context creating this session.
* @return A new builder.
*/
public static Builder create(HtmlSerializer ctx) {
return new Builder(ctx);
}
//-----------------------------------------------------------------------------------------------------------------
// Builder
//-----------------------------------------------------------------------------------------------------------------
/**
* Builder class.
*/
@FluentSetters
public static class Builder extends XmlSerializerSession.Builder {
HtmlSerializer ctx;
/**
* Constructor
*
* @param ctx The context creating this session.
*/
protected Builder(HtmlSerializer ctx) {
super(ctx);
this.ctx = ctx;
}
@Override
public HtmlSerializerSession build() {
return new HtmlSerializerSession(this);
}
//
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder apply(Class type, Consumer apply) {
super.apply(type, apply);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder debug(Boolean value) {
super.debug(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder properties(Map value) {
super.properties(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder property(String key, Object value) {
super.property(key, value);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder unmodifiable() {
super.unmodifiable();
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder locale(Locale value) {
super.locale(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder localeDefault(Locale value) {
super.localeDefault(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder mediaType(MediaType value) {
super.mediaType(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder mediaTypeDefault(MediaType value) {
super.mediaTypeDefault(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder timeZone(TimeZone value) {
super.timeZone(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder timeZoneDefault(TimeZone value) {
super.timeZoneDefault(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
public Builder javaMethod(Method value) {
super.javaMethod(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
public Builder resolver(VarResolverSession value) {
super.resolver(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
public Builder schema(HttpPartSchema value) {
super.schema(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
public Builder schemaDefault(HttpPartSchema value) {
super.schemaDefault(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
public Builder uriContext(UriContext value) {
super.uriContext(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */
public Builder fileCharset(Charset value) {
super.fileCharset(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */
public Builder streamCharset(Charset value) {
super.streamCharset(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */
public Builder useWhitespace(Boolean value) {
super.useWhitespace(value);
return this;
}
//
}
//-----------------------------------------------------------------------------------------------------------------
// Instance
//-----------------------------------------------------------------------------------------------------------------
private final HtmlSerializer ctx;
private final Pattern urlPattern = Pattern.compile("http[s]?\\:\\/\\/.*");
private final Pattern labelPattern;
/**
* Constructor.
*
* @param builder The builder for this object.
*/
protected HtmlSerializerSession(Builder builder) {
super(builder);
ctx = builder.ctx;
labelPattern = Pattern.compile("[\\?\\&]" + Pattern.quote(ctx.getLabelParameter()) + "=([^\\&]*)");
}
/**
* Converts the specified output target object to an {@link HtmlWriter}.
*
* @param out The output target object.
* @return The output target object wrapped in an {@link HtmlWriter}.
* @throws IOException Thrown by underlying stream.
*/
protected final HtmlWriter getHtmlWriter(SerializerPipe out) throws IOException {
Object output = out.getRawOutput();
if (output instanceof HtmlWriter)
return (HtmlWriter)output;
HtmlWriter w = new HtmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(),
getUriResolver());
out.setWriter(w);
return w;
}
/**
* Returns true if the specified object is a URL.
*
* @param cm The ClassMeta of the object being serialized.
* @param pMeta
* The property metadata of the bean property of the object.
* Can be null if the object isn't from a bean property.
* @param o The object.
* @return true if the specified object is a URL.
*/
public boolean isUri(ClassMeta> cm, BeanPropertyMeta pMeta, Object o) {
if (cm.isUri() || (pMeta != null && pMeta.isUri()))
return true;
if (isDetectLinksInStrings() && o instanceof CharSequence && urlPattern.matcher(o.toString()).matches())
return true;
return false;
}
/**
* Returns the anchor text to use for the specified URL object.
*
* @param pMeta
* The property metadata of the bean property of the object.
* Can be null if the object isn't from a bean property.
* @param o The URL object.
* @return The anchor text to use for the specified URL object.
*/
public String getAnchorText(BeanPropertyMeta pMeta, Object o) {
String s = o.toString();
if (isDetectLabelParameters()) {
Matcher m = labelPattern.matcher(s);
if (m.find())
return urlDecode(m.group(1));
}
switch (getUriAnchorText()) {
case LAST_TOKEN:
s = resolveUri(s);
if (s.indexOf('/') != -1)
s = s.substring(s.lastIndexOf('/')+1);
if (s.indexOf('?') != -1)
s = s.substring(0, s.indexOf('?'));
if (s.indexOf('#') != -1)
s = s.substring(0, s.indexOf('#'));
if (s.isEmpty())
s = "/";
return urlDecode(s);
case URI_ANCHOR:
if (s.indexOf('#') != -1)
s = s.substring(s.lastIndexOf('#')+1);
return urlDecode(s);
case PROPERTY_NAME:
return pMeta == null ? s : pMeta.getName();
case URI:
return resolveUri(s);
case CONTEXT_RELATIVE:
return relativizeUri("context:/", s);
case SERVLET_RELATIVE:
return relativizeUri("servlet:/", s);
case PATH_RELATIVE:
return relativizeUri("request:/", s);
default /* TO_STRING */:
return s;
}
}
@Override /* XmlSerializer */
public boolean isHtmlMode() {
return true;
}
@Override /* Serializer */
protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
doSerialize(o, getHtmlWriter(out));
}
/**
* Main serialization routine.
*
* @param session The serialization context object.
* @param o The object being serialized.
* @param w The writer to serialize to.
* @return The same writer passed in.
* @throws IOException If a problem occurred trying to send output to the writer.
*/
private XmlWriter doSerialize(Object o, XmlWriter w) throws IOException, SerializeException {
serializeAnything(w, o, getExpectedRootType(o), null, null, getInitialDepth()-1, true, false);
return w;
}
@SuppressWarnings({ "rawtypes" })
@Override /* XmlSerializerSession */
protected ContentResult serializeAnything(
XmlWriter out,
Object o,
ClassMeta> eType,
String keyName,
String elementName,
Namespace elementNamespace,
boolean addNamespaceUris,
XmlFormat format,
boolean isMixed,
boolean preserveWhitespace,
BeanPropertyMeta pMeta) throws SerializeException {
// If this is a bean, then we want to serialize it as HTML unless it's @Html(format=XML).
ClassMeta> type = push2(elementName, o, eType);
pop();
if (type == null)
type = object();
else if (type.isDelegate())
type = ((Delegate)o).getClassMeta();
ObjectSwap swap = type.getSwap(this);
if (swap != null) {
o = swap(swap, o);
type = swap.getSwapClassMeta(this);
if (type.isObject())
type = getClassMetaForObject(o);
}
HtmlClassMeta cHtml = getHtmlClassMeta(type);
if (type.isMapOrBean() && ! cHtml.isXml())
return serializeAnything(out, o, eType, elementName, pMeta, 0, false, false);
return super.serializeAnything(out, o, eType, keyName, elementName, elementNamespace, addNamespaceUris, format, isMixed, preserveWhitespace, pMeta);
}
/**
* Serialize the specified object to the specified writer.
*
* @param out The writer.
* @param o The object to serialize.
* @param eType The expected type of the object if this is a bean property.
* @param name
* The attribute name of this object if this object was a field in a JSON object (i.e. key of a
* {@link java.util.Map.Entry} or property name of a bean).
* @param pMeta The bean property being serialized, or null if we're not serializing a bean property.
* @param xIndent The current indentation value.
* @param isRoot true if this is the root element of the document.
* @param nlIfElement true if we should add a newline to the output before serializing only if the object is an element and not text.
* @return The type of content encountered. Either simple (no whitespace) or normal (elements with whitespace).
* @throws SerializeException Generic serialization error occurred.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected ContentResult serializeAnything(XmlWriter out, Object o,
ClassMeta> eType, String name, BeanPropertyMeta pMeta, int xIndent, boolean isRoot, boolean nlIfElement) throws SerializeException {
ClassMeta> aType = null; // The actual type
ClassMeta> wType = null; // The wrapped type (delegate)
ClassMeta> sType = object(); // The serialized type
if (eType == null)
eType = object();
aType = push2(name, o, eType);
// Handle recursion
if (aType == null) {
o = null;
aType = object();
}
// Handle Optional
if (isOptional(aType)) {
o = getOptionalValue(o);
eType = getOptionalType(eType);
aType = getClassMetaForObject(o, object());
}
indent += xIndent;
ContentResult cr = CR_ELEMENTS;
// Determine the type.
if (o == null || (aType.isChar() && ((Character)o).charValue() == 0)) {
out.tag("null");
cr = ContentResult.CR_MIXED;
} else {
if (aType.isDelegate()) {
wType = aType;
aType = ((Delegate)o).getClassMeta();
}
sType = aType;
String typeName = null;
if (isAddBeanTypes() && ! eType.equals(aType))
typeName = aType.getDictionaryName();
// Swap if necessary
ObjectSwap swap = aType.getSwap(this);
if (swap != null) {
o = swap(swap, o);
sType = swap.getSwapClassMeta(this);
// If the getSwapClass() method returns Object, we need to figure out
// the actual type now.
if (sType.isObject())
sType = getClassMetaForObject(o);
}
// Handle the case where we're serializing a raw stream.
if (sType.isReader() || sType.isInputStream()) {
pop();
indent -= xIndent;
if (sType.isReader())
pipe((Reader)o, out, SerializerSession::handleThrown);
else
pipe((InputStream)o, out, SerializerSession::handleThrown);
return ContentResult.CR_MIXED;
}
HtmlClassMeta cHtml = getHtmlClassMeta(sType);
HtmlBeanPropertyMeta bpHtml = getHtmlBeanPropertyMeta(pMeta);
HtmlRender render = firstNonNull(bpHtml.getRender(), cHtml.getRender());
if (render != null) {
Object o2 = render.getContent(this, o);
if (o2 != o) {
indent -= xIndent;
pop();
out.nl(indent);
return serializeAnything(out, o2, null, typeName, null, xIndent, false, false);
}
}
if (cHtml.isXml() || bpHtml.isXml()) {
pop();
indent++;
if (nlIfElement)
out.nl(0);
super.serializeAnything(out, o, null, null, null, null, false, XmlFormat.MIXED, false, false, null);
indent -= xIndent+1;
return cr;
} else if (cHtml.isPlainText() || bpHtml.isPlainText()) {
out.w(o == null ? "null" : o.toString());
cr = CR_MIXED;
} else if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) {
out.tag("null");
cr = CR_MIXED;
} else if (sType.isNumber()) {
if (eType.isNumber() && ! isRoot)
out.append(o);
else
out.sTag("number").append(o).eTag("number");
cr = CR_MIXED;
} else if (sType.isBoolean()) {
if (eType.isBoolean() && ! isRoot)
out.append(o);
else
out.sTag("boolean").append(o).eTag("boolean");
cr = CR_MIXED;
} else if (sType.isMap() || (wType != null && wType.isMap())) {
out.nlIf(! isRoot, xIndent+1);
if (o instanceof BeanMap)
serializeBeanMap(out, (BeanMap)o, eType, pMeta);
else
serializeMap(out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), typeName, pMeta);
} else if (sType.isBean()) {
BeanMap m = toBeanMap(o);
if (aType.hasAnnotation(HtmlLink.class)) {
Value uriProperty = Value.empty(), nameProperty = Value.empty();
aType.forEachAnnotation(HtmlLink.class, x -> isNotEmpty(x.uriProperty()), x -> uriProperty.set(x.uriProperty()));
aType.forEachAnnotation(HtmlLink.class, x -> isNotEmpty(x.nameProperty()), x -> nameProperty.set(x.nameProperty()));
Object urlProp = m.get(uriProperty.orElse(""));
Object nameProp = m.get(nameProperty.orElse(""));
out.oTag("a").attrUri("href", urlProp).w('>').text(nameProp).eTag("a");
cr = CR_MIXED;
} else {
out.nlIf(! isRoot, xIndent+2);
serializeBeanMap(out, m, eType, pMeta);
}
} else if (sType.isCollection() || sType.isArray() || (wType != null && wType.isCollection())) {
out.nlIf(! isRoot, xIndent+1);
serializeCollection(out, o, sType, eType, name, pMeta);
} else if (isUri(sType, pMeta, o)) {
String label = getAnchorText(pMeta, o);
out.oTag("a").attrUri("href", o).w('>');
out.text(label);
out.eTag("a");
cr = CR_MIXED;
} else {
if (isRoot)
out.sTag("string").text(toString(o)).eTag("string");
else
out.text(toString(o));
cr = CR_MIXED;
}
}
pop();
indent -= xIndent;
return cr;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void serializeMap(XmlWriter out, Map m, ClassMeta> sType,
ClassMeta> eKeyType, ClassMeta> eValueType, String typeName, BeanPropertyMeta ppMeta) throws SerializeException {
ClassMeta> keyType = eKeyType == null ? string() : eKeyType;
ClassMeta> valueType = eValueType == null ? object() : eValueType;
ClassMeta> aType = getClassMetaForObject(m); // The actual type
HtmlClassMeta cHtml = getHtmlClassMeta(aType);
HtmlBeanPropertyMeta bpHtml = getHtmlBeanPropertyMeta(ppMeta);
int i = indent;
out.oTag(i, "table");
if (typeName != null && ppMeta != null && ppMeta.getClassMeta() != aType)
out.attr(getBeanTypePropertyName(sType), typeName);
out.append(">").nl(i+1);
if (isAddKeyValueTableHeaders() && ! (cHtml.isNoTableHeaders() || bpHtml.isNoTableHeaders())) {
out.sTag(i+1, "tr").nl(i+2);
out.sTag(i+2, "th").append("key").eTag("th").nl(i+3);
out.sTag(i+2, "th").append("value").eTag("th").nl(i+3);
out.ie(i+1).eTag("tr").nl(i+2);
}
forEachEntry(m, x -> serializeMapEntry(out, x, keyType, valueType, i, ppMeta));
out.ie(i).eTag("table").nl(i);
}
@SuppressWarnings("rawtypes")
private void serializeMapEntry(XmlWriter out, Map.Entry e, ClassMeta> keyType, ClassMeta> valueType, int i, BeanPropertyMeta ppMeta) throws SerializeException {
Object key = generalize(e.getKey(), keyType);
Object value = null;
try {
value = e.getValue();
} catch (StackOverflowError t) {
throw t;
} catch (Throwable t) {
onError(t, "Could not call getValue() on property ''{0}'', {1}", e.getKey(), t.getLocalizedMessage());
}
String link = getLink(ppMeta);
String style = getStyle(this, ppMeta, value);
out.sTag(i+1, "tr").nl(i+2);
out.oTag(i+2, "td");
if (style != null)
out.attr("style", style);
out.cTag();
if (link != null)
out.oTag(i+3, "a").attrUri("href", link.replace("{#}", stringify(value))).cTag();
ContentResult cr = serializeAnything(out, key, keyType, null, null, 2, false, false);
if (link != null)
out.eTag("a");
if (cr == CR_ELEMENTS)
out.i(i+2);
out.eTag("td").nl(i+2);
out.sTag(i+2, "td");
cr = serializeAnything(out, value, valueType, (key == null ? "_x0000_" : toString(key)), null, 2, false, true);
if (cr == CR_ELEMENTS)
out.ie(i+2);
out.eTag("td").nl(i+2);
out.ie(i+1).eTag("tr").nl(i+1);
}
private void serializeBeanMap(XmlWriter out, BeanMap> m, ClassMeta> eType, BeanPropertyMeta ppMeta) throws SerializeException {
HtmlClassMeta cHtml = getHtmlClassMeta(m.getClassMeta());
HtmlBeanPropertyMeta bpHtml = getHtmlBeanPropertyMeta(ppMeta);
int i = indent;
out.oTag(i, "table");
String typeName = m.getMeta().getDictionaryName();
if (typeName != null && eType != m.getClassMeta())
out.attr(getBeanTypePropertyName(m.getClassMeta()), typeName);
out.w('>').nl(i);
if (isAddKeyValueTableHeaders() && ! (cHtml.isNoTableHeaders() || bpHtml.isNoTableHeaders())) {
out.sTag(i+1, "tr").nl(i+1);
out.sTag(i+2, "th").append("key").eTag("th").nl(i+2);
out.sTag(i+2, "th").append("value").eTag("th").nl(i+2);
out.ie(i+1).eTag("tr").nl(i+1);
}
Predicate
© 2015 - 2025 Weber Informatics LLC | Privacy Policy