001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau; 014 015 016import org.apache.juneau.parser.*; 017 018/** 019 * Parent class for all classes that traverse POJOs. 020 * 021 * <h5 class='topic'>Description</h5> 022 * 023 * Base class that serves as the parent class for all serializers and other classes that traverse POJOs. 024 */ 025public abstract class BeanTraverseContext extends BeanContext { 026 027 //------------------------------------------------------------------------------------------------------------------- 028 // Configurable properties 029 //------------------------------------------------------------------------------------------------------------------- 030 031 private static final String PREFIX = "BeanTraverseContext."; 032 033 /** 034 * Configuration property: Automatically detect POJO recursions. 035 * 036 * <h5 class='section'>Property:</h5> 037 * <ul> 038 * <li><b>Name:</b> <js>"BeanTraverseContext.detectRecursions.b"</js> 039 * <li><b>Data type:</b> <code>Boolean</code> 040 * <li><b>Default:</b> <jk>false</jk> 041 * <li><b>Session property:</b> <jk>false</jk> 042 * <li><b>Methods:</b> 043 * <ul> 044 * <li class='jm'>{@link BeanTraverseBuilder#detectRecursions(boolean)} 045 * <li class='jm'>{@link BeanTraverseBuilder#detectRecursions()} 046 * </ul> 047 * </ul> 048 * 049 * <h5 class='section'>Description:</h5> 050 * <p> 051 * Specifies that recursions should be checked for during traversal. 052 * 053 * <p> 054 * Recursions can occur when traversing models that aren't true trees but rather contain loops. 055 * <br>In general, unchecked recursions cause stack-overflow-errors. 056 * <br>These show up as {@link ParseException ParseExceptions} with the message <js>"Depth too deep. Stack overflow occurred."</js>. 057 * 058 * <p> 059 * The behavior when recursions are detected depends on the value for {@link #BEANTRAVERSE_ignoreRecursions}. 060 * 061 * <p> 062 * For example, if a model contains the links A->B->C->A, then the JSON generated will look like 063 * the following when <jsf>BEANTRAVERSE_ignoreRecursions</jsf> is <jk>true</jk>... 064 * 065 * <p class='bcode w800'> 066 * {A:{B:{C:<jk>null</jk>}}} 067 * </p> 068 * 069 * <h5 class='section'>Notes:</h5> 070 * <ul class='spaced-list'> 071 * <li> 072 * Checking for recursion can cause a small performance penalty. 073 * </ul> 074 * 075 * <h5 class='section'>Example:</h5> 076 * <p class='bcode w800'> 077 * <jc>// Create a serializer that never adds _type to nodes.</jc> 078 * WriterSerializer s = JsonSerializer 079 * .<jsm>create</jsm>() 080 * .detectRecursions() 081 * .ignoreRecursions() 082 * .build(); 083 * 084 * <jc>// Same, but use property.</jc> 085 * WriterSerializer s = JsonSerializer 086 * .<jsm>create</jsm>() 087 * .set(<jsf>BEANTRAVERSE_detectRecursions</jsf>, <jk>true</jk>) 088 * .set(<jsf>BEANTRAVERSE_ignoreRecursions</jsf>, <jk>true</jk>) 089 * .build(); 090 * 091 * <jc>// Create a POJO model with a recursive loop.</jc> 092 * <jk>public class</jk> A { 093 * <jk>public</jk> Object <jf>f</jf>; 094 * } 095 * A a = <jk>new</jk> A(); 096 * a.<jf>f</jf> = a; 097 * 098 * <jc>// Produces "{f:null}"</jc> 099 * String json = s.serialize(a); 100 * </p> 101 */ 102 public static final String BEANTRAVERSE_detectRecursions = PREFIX + "detectRecursions.b"; 103 104 /** 105 * Configuration property: Ignore recursion errors. 106 * 107 * <h5 class='section'>Property:</h5> 108 * <ul> 109 * <li><b>Name:</b> <js>"BeanTraverseContext.ignoreRecursions.b"</js> 110 * <li><b>Data type:</b> <code>Boolean</code> 111 * <li><b>Default:</b> <jk>false</jk> 112 * <li><b>Session property:</b> <jk>false</jk> 113 * <li><b>Methods:</b> 114 * <ul> 115 * <li class='jm'>{@link BeanTraverseBuilder#ignoreRecursions(boolean)} 116 * <li class='jm'>{@link BeanTraverseBuilder#ignoreRecursions()} 117 * </ul> 118 * </ul> 119 * 120 * <h5 class='section'>Description:</h5> 121 * <p> 122 * Used in conjunction with {@link #BEANTRAVERSE_detectRecursions}. 123 * <br>Setting is ignored if <jsf>BEANTRAVERSE_detectRecursions</jsf> is <jk>false</jk>. 124 * 125 * <p> 126 * If <jk>true</jk>, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 127 * <br>Otherwise, a {@link BeanRecursionException} is thrown with the message <js>"Recursion occurred, stack=..."</js>. 128 */ 129 public static final String BEANTRAVERSE_ignoreRecursions = PREFIX + "ignoreRecursions.b"; 130 131 /** 132 * Configuration property: Initial depth. 133 * 134 * <h5 class='section'>Property:</h5> 135 * <ul> 136 * <li><b>Name:</b> <js>"BeanTraverseContext.initialDepth.i"</js> 137 * <li><b>Data type:</b> <code>Integer</code> 138 * <li><b>Default:</b> <code>0</code> 139 * <li><b>Session property:</b> <jk>false</jk> 140 * <li><b>Methods:</b> 141 * <ul> 142 * <li class='jm'>{@link BeanTraverseBuilder#initialDepth(int)} 143 * </ul> 144 * </ul> 145 * 146 * <h5 class='section'>Description:</h5> 147 * <p> 148 * The initial indentation level at the root. 149 * <br>Useful when constructing document fragments that need to be indented at a certain level. 150 * 151 * <h5 class='section'>Example:</h5> 152 * <p class='bcode w800'> 153 * <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc> 154 * WriterSerializer s = JsonSerializer 155 * .<jsm>create</jsm>() 156 * .ws() 157 * .initialDepth(2) 158 * .build(); 159 * 160 * <jc>// Same, but use property.</jc> 161 * WriterSerializer s = JsonSerializer 162 * .<jsm>create</jsm>() 163 * .set(<jsf>BEANTRAVERSE_useWhitespace</jsf>, <jk>true</jk>) 164 * .set(<jsf>BEANTRAVERSE_initialDepth</jsf>, 2) 165 * .build(); 166 * 167 * <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc> 168 * String json = s.serialize(<jk>new</jk> MyBean()); 169 * </p> 170 */ 171 public static final String BEANTRAVERSE_initialDepth = PREFIX + "initialDepth.i"; 172 173 /** 174 * Configuration property: Max traversal depth. 175 * 176 * <h5 class='section'>Property:</h5> 177 * <ul> 178 * <li><b>Name:</b> <js>"BeanTraverseContext.maxDepth.i"</js> 179 * <li><b>Data type:</b> <code>Integer</code> 180 * <li><b>Default:</b> <code>100</code> 181 * <li><b>Session property:</b> <jk>false</jk> 182 * <li><b>Methods:</b> 183 * <ul> 184 * <li class='jm'>{@link BeanTraverseBuilder#maxDepth(int)} 185 * </ul> 186 * </ul> 187 * 188 * <h5 class='section'>Description:</h5> 189 * <p> 190 * Abort traversal if specified depth is reached in the POJO tree. 191 * <br>If this depth is exceeded, an exception is thrown. 192 * 193 * <h5 class='section'>Example:</h5> 194 * <p class='bcode w800'> 195 * <jc>// Create a serializer that throws an exception if the depth is greater than 20.</jc> 196 * WriterSerializer s = JsonSerializer 197 * .<jsm>create</jsm>() 198 * .maxDepth(20) 199 * .build(); 200 * 201 * <jc>// Same, but use property.</jc> 202 * WriterSerializer s = JsonSerializer 203 * .<jsm>create</jsm>() 204 * .set(<jsf>BEANTRAVERSE_maxDepth</jsf>, 20) 205 * .build(); 206 * </p> 207 */ 208 public static final String BEANTRAVERSE_maxDepth = PREFIX + "maxDepth.i"; 209 210 //------------------------------------------------------------------------------------------------------------------- 211 // Instance 212 //------------------------------------------------------------------------------------------------------------------- 213 214 private final int initialDepth, maxDepth; 215 private final boolean 216 detectRecursions, 217 ignoreRecursions; 218 219 /** 220 * Constructor 221 * 222 * @param ps 223 * The property store containing all the settings for this object. 224 */ 225 protected BeanTraverseContext(PropertyStore ps) { 226 super(ps); 227 228 maxDepth = getIntegerProperty(BEANTRAVERSE_maxDepth, 100); 229 initialDepth = getIntegerProperty(BEANTRAVERSE_initialDepth, 0); 230 detectRecursions = getBooleanProperty(BEANTRAVERSE_detectRecursions, false); 231 ignoreRecursions = getBooleanProperty(BEANTRAVERSE_ignoreRecursions, false); 232 } 233 234 @Override /* Context */ 235 public BeanTraverseBuilder builder() { 236 return null; 237 } 238 239 @Override /* Context */ 240 public BeanTraverseSession createSession(BeanSessionArgs args) { 241 return new BeanTraverseSession(this, args); 242 } 243 244 245 //----------------------------------------------------------------------------------------------------------------- 246 // Properties 247 //----------------------------------------------------------------------------------------------------------------- 248 249 /** 250 * Configuration property: Initial depth. 251 * 252 * @see #BEANTRAVERSE_initialDepth 253 * @return 254 * The initial indentation level at the root. 255 */ 256 protected final int getInitialDepth() { 257 return initialDepth; 258 } 259 260 /** 261 * Configuration property: Max traversal depth. 262 * 263 * @see #BEANTRAVERSE_maxDepth 264 * @return 265 * The depth at which traversal is aborted if depth is reached in the POJO tree. 266 * <br>If this depth is exceeded, an exception is thrown. 267 */ 268 protected final int getMaxDepth() { 269 return maxDepth; 270 } 271 272 /** 273 * Configuration property: Automatically detect POJO recursions. 274 * @see #BEANTRAVERSE_detectRecursions 275 * @return 276 * <jk>true</jk> if recursions should be checked for during traversal. 277 */ 278 protected final boolean isDetectRecursions() { 279 return detectRecursions; 280 } 281 282 /** 283 * Configuration property: Ignore recursion errors. 284 * 285 * @see #BEANTRAVERSE_ignoreRecursions 286 * @return 287 * <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 288 * <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>. 289 */ 290 protected final boolean isIgnoreRecursions() { 291 return ignoreRecursions; 292 } 293 294 @Override /* Context */ 295 public ObjectMap asMap() { 296 return super.asMap() 297 .append("BeanTraverseContext", new ObjectMap() 298 .append("maxDepth", maxDepth) 299 .append("initialDepth", initialDepth) 300 .append("detectRecursions", detectRecursions) 301 .append("ignoreRecursions", ignoreRecursions) 302 ); 303 } 304}