blob: e600f784b582ea47f62413a8e4c835d86ec7db95 [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.juneau;
import org.apache.juneau.annotation.*;
import org.apache.juneau.parser.*;
/**
* Parent class for all classes that traverse POJOs.
*
* <h5 class='topic'>Description</h5>
*
* Base class that serves as the parent class for all serializers and other classes that traverse POJOs.
*/
@ConfigurableContext
public abstract class BeanTraverseContext extends BeanContext {
//-------------------------------------------------------------------------------------------------------------------
// Configurable properties
//-------------------------------------------------------------------------------------------------------------------
static final String PREFIX = "BeanTraverseContext";
/**
* Configuration property: Automatically detect POJO recursions.
*
* <h5 class='section'>Property:</h5>
* <ul>
* <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_detectRecursions BEANTRAVERSE_detectRecursions}
* <li><b>Name:</b> <js>"BeanTraverseContext.detectRecursions.b"</js>
* <li><b>Data type:</b> <jk>boolean</jk>
* <li><b>System property:</b> <c>BeanTraverseContext.detectRecursions</c>
* <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_DETECTRECURSIONS</c>
* <li><b>Default:</b> <jk>false</jk>
* <li><b>Session property:</b> <jk>false</jk>
* <li><b>Annotations:</b>
* <ul>
* <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#detectRecursions()}
* </ul>
* <li><b>Methods:</b>
* <ul>
* <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#detectRecursions(boolean)}
* <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#detectRecursions()}
* </ul>
* </ul>
*
* <h5 class='section'>Description:</h5>
* <p>
* Specifies that recursions should be checked for during traversal.
*
* <p>
* Recursions can occur when traversing models that aren't true trees but rather contain loops.
* <br>In general, unchecked recursions cause stack-overflow-errors.
* <br>These show up as {@link ParseException ParseExceptions} with the message <js>"Depth too deep. Stack overflow occurred."</js>.
*
* <p>
* The behavior when recursions are detected depends on the value for {@link #BEANTRAVERSE_ignoreRecursions}.
*
* <p>
* For example, if a model contains the links A-&gt;B-&gt;C-&gt;A, then the JSON generated will look like
* the following when <jsf>BEANTRAVERSE_ignoreRecursions</jsf> is <jk>true</jk>...
*
* <p class='bcode w800'>
* {A:{B:{C:<jk>null</jk>}}}
* </p>
*
* <ul class='notes'>
* <li>
* Checking for recursion can cause a small performance penalty.
* </ul>
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Create a serializer that never adds _type to nodes.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .detectRecursions()
* .ignoreRecursions()
* .build();
*
* <jc>// Same, but use property.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .set(<jsf>BEANTRAVERSE_detectRecursions</jsf>, <jk>true</jk>)
* .set(<jsf>BEANTRAVERSE_ignoreRecursions</jsf>, <jk>true</jk>)
* .build();
*
* <jc>// Create a POJO model with a recursive loop.</jc>
* <jk>public class</jk> A {
* <jk>public</jk> Object <jf>f</jf>;
* }
* A a = <jk>new</jk> A();
* a.<jf>f</jf> = a;
*
* <jc>// Produces "{f:null}"</jc>
* String json = s.serialize(a);
* </p>
*/
public static final String BEANTRAVERSE_detectRecursions = PREFIX + ".detectRecursions.b";
/**
* Configuration property: Ignore recursion errors.
*
* <h5 class='section'>Property:</h5>
* <ul>
* <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_ignoreRecursions BEANTRAVERSE_ignoreRecursions}
* <li><b>Name:</b> <js>"BeanTraverseContext.ignoreRecursions.b"</js>
* <li><b>Data type:</b> <jk>boolean</jk>
* <li><b>System property:</b> <c>BeanTraverseContext.ignoreRecursions</c>
* <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_IGNORERECURSIONS</c>
* <li><b>Default:</b> <jk>false</jk>
* <li><b>Session property:</b> <jk>false</jk>
* <li><b>Annotations:</b>
* <ul>
* <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#ignoreRecursions()}
* </ul>
* <li><b>Methods:</b>
* <ul>
* <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions(boolean)}
* <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions()}
* </ul>
* </ul>
*
* <h5 class='section'>Description:</h5>
* <p>
* Used in conjunction with {@link #BEANTRAVERSE_detectRecursions}.
* <br>Setting is ignored if <jsf>BEANTRAVERSE_detectRecursions</jsf> is <jk>false</jk>.
*
* <p>
* If <jk>true</jk>, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
* <br>Otherwise, a {@link BeanRecursionException} is thrown with the message <js>"Recursion occurred, stack=..."</js>.
*/
public static final String BEANTRAVERSE_ignoreRecursions = PREFIX + ".ignoreRecursions.b";
/**
* Configuration property: Initial depth.
*
* <h5 class='section'>Property:</h5>
* <ul>
* <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_initialDepth BEANTRAVERSE_initialDepth}
* <li><b>Name:</b> <js>"BeanTraverseContext.initialDepth.i"</js>
* <li><b>Data type:</b> <jk>int</jk>
* <li><b>System property:</b> <c>BeanTraverseContext.initialDepth</c>
* <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_INITIALDEPTH</c>
* <li><b>Default:</b> <c>0</c>
* <li><b>Session property:</b> <jk>false</jk>
* <li><b>Annotations:</b>
* <ul>
* <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#initialDepth()}
* </ul>
* <li><b>Methods:</b>
* <ul>
* <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#initialDepth(int)}
* </ul>
* </ul>
*
* <h5 class='section'>Description:</h5>
* <p>
* The initial indentation level at the root.
* <br>Useful when constructing document fragments that need to be indented at a certain level.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .ws()
* .initialDepth(2)
* .build();
*
* <jc>// Same, but use property.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .set(<jsf>BEANTRAVERSE_useWhitespace</jsf>, <jk>true</jk>)
* .set(<jsf>BEANTRAVERSE_initialDepth</jsf>, 2)
* .build();
*
* <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc>
* String json = s.serialize(<jk>new</jk> MyBean());
* </p>
*/
public static final String BEANTRAVERSE_initialDepth = PREFIX + ".initialDepth.i";
/**
* Configuration property: Max traversal depth.
*
* <h5 class='section'>Property:</h5>
* <ul>
* <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_maxDepth BEANTRAVERSE_maxDepth}
* <li><b>Name:</b> <js>"BeanTraverseContext.maxDepth.i"</js>
* <li><b>Data type:</b> <jk>int</jk>
* <li><b>System property:</b> <c>BeanTraverseContext.maxDepth</c>
* <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_MAXDEPTH</c>
* <li><b>Default:</b> <c>100</c>
* <li><b>Session property:</b> <jk>false</jk>
* <li><b>Annotations:</b>
* <ul>
* <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#maxDepth()}
* </ul>
* <li><b>Methods:</b>
* <ul>
* <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#maxDepth(int)}
* </ul>
* </ul>
*
* <h5 class='section'>Description:</h5>
* <p>
* Abort traversal if specified depth is reached in the POJO tree.
* <br>If this depth is exceeded, an exception is thrown.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Create a serializer that throws an exception if the depth is greater than 20.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .maxDepth(20)
* .build();
*
* <jc>// Same, but use property.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .set(<jsf>BEANTRAVERSE_maxDepth</jsf>, 20)
* .build();
* </p>
*/
public static final String BEANTRAVERSE_maxDepth = PREFIX + ".maxDepth.i";
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final int initialDepth, maxDepth;
private final boolean
detectRecursions,
ignoreRecursions;
/**
* Constructor
*
* @param ps
* The property store containing all the settings for this object.
*/
protected BeanTraverseContext(PropertyStore ps) {
super(ps);
maxDepth = getIntegerProperty(BEANTRAVERSE_maxDepth, 100);
initialDepth = getIntegerProperty(BEANTRAVERSE_initialDepth, 0);
detectRecursions = getBooleanProperty(BEANTRAVERSE_detectRecursions, false);
ignoreRecursions = getBooleanProperty(BEANTRAVERSE_ignoreRecursions, false);
}
@Override /* Context */
public BeanTraverseBuilder builder() {
return null;
}
@Override /* Context */
public BeanTraverseSession createSession() {
return new BeanTraverseSession(this, createDefaultSessionArgs());
}
@Override /* Context */
public BeanTraverseSession createSession(BeanSessionArgs args) {
return new BeanTraverseSession(this, args);
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Configuration property: Automatically detect POJO recursions.
*
* @see #BEANTRAVERSE_detectRecursions
* @return
* <jk>true</jk> if recursions should be checked for during traversal.
*/
protected final boolean isDetectRecursions() {
return detectRecursions;
}
/**
* Configuration property: Ignore recursion errors.
*
* @see #BEANTRAVERSE_ignoreRecursions
* @return
* <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
* <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>.
*/
protected final boolean isIgnoreRecursions() {
return ignoreRecursions;
}
/**
* Configuration property: Initial depth.
*
* @see #BEANTRAVERSE_initialDepth
* @return
* The initial indentation level at the root.
*/
protected final int getInitialDepth() {
return initialDepth;
}
/**
* Configuration property: Max traversal depth.
*
* @see #BEANTRAVERSE_maxDepth
* @return
* The depth at which traversal is aborted if depth is reached in the POJO tree.
* <br>If this depth is exceeded, an exception is thrown.
*/
protected final int getMaxDepth() {
return maxDepth;
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Context */
public ObjectMap toMap() {
return super.toMap()
.append("BeanTraverseContext", new DefaultFilteringObjectMap()
.append("detectRecursions", detectRecursions)
.append("maxDepth", maxDepth)
.append("ignoreRecursions", ignoreRecursions)
.append("initialDepth", initialDepth)
);
}
}