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.json; 014 015import static org.apache.juneau.json.JsonSerializer.*; 016 017import java.util.*; 018 019import org.apache.juneau.*; 020import org.apache.juneau.internal.*; 021import org.apache.juneau.serializer.*; 022import org.apache.juneau.transform.*; 023 024/** 025 * Session object that lives for the duration of a single use of {@link JsonSerializer}. 026 * 027 * <p> 028 * This class is NOT thread safe. 029 * It is typically discarded after one-time use although it can be reused within the same thread. 030 */ 031public class JsonSerializerSession extends WriterSerializerSession { 032 033 private final boolean 034 simpleMode, 035 escapeSolidus, 036 addBeanTypeProperties; 037 038 /** 039 * Create a new session using properties specified in the context. 040 * 041 * @param ctx 042 * The context creating this session object. 043 * The context contains all the configuration settings for this object. 044 * @param args 045 * Runtime arguments. 046 * These specify session-level information such as locale and URI context. 047 * It also include session-level properties that override the properties defined on the bean and 048 * serializer contexts. 049 */ 050 protected JsonSerializerSession(JsonSerializer ctx, SerializerSessionArgs args) { 051 super(ctx, args); 052 simpleMode = getProperty(JSON_simpleMode, boolean.class, ctx.simpleMode); 053 escapeSolidus = getProperty(JSON_escapeSolidus, boolean.class, ctx.escapeSolidus); 054 addBeanTypeProperties = getProperty(JSON_addBeanTypeProperties, boolean.class, ctx.addBeanTypeProperties); 055 } 056 057 @Override /* Session */ 058 public ObjectMap asMap() { 059 return super.asMap() 060 .append("JsonSerializerSession", new ObjectMap() 061 .append("addBeanTypeProperties", addBeanTypeProperties) 062 .append("escapeSolidus", escapeSolidus) 063 .append("simpleMode", simpleMode) 064 ); 065 } 066 067 @Override /* SerializerSesssion */ 068 protected void doSerialize(SerializerPipe out, Object o) throws Exception { 069 serializeAnything(getJsonWriter(out), o, getExpectedRootType(o), "root", null); 070 } 071 072 /* 073 * Workhorse method. 074 * Determines the type of object, and then calls the appropriate type-specific serialization method. 075 */ 076 @SuppressWarnings({ "rawtypes", "unchecked" }) 077 SerializerWriter serializeAnything(JsonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception { 078 079 if (o == null) { 080 out.append("null"); 081 return out; 082 } 083 084 if (eType == null) 085 eType = object(); 086 087 ClassMeta<?> aType; // The actual type 088 ClassMeta<?> sType; // The serialized type 089 090 aType = push(attrName, o, eType); 091 boolean isRecursion = aType == null; 092 093 // Handle recursion 094 if (aType == null) { 095 o = null; 096 aType = object(); 097 } 098 099 sType = aType; 100 String typeName = getBeanTypeName(eType, aType, pMeta); 101 102 // Swap if necessary 103 PojoSwap swap = aType.getPojoSwap(this); 104 if (swap != null) { 105 o = swap.swap(this, o); 106 sType = swap.getSwapClassMeta(this); 107 108 // If the getSwapClass() method returns Object, we need to figure out 109 // the actual type now. 110 if (sType.isObject()) 111 sType = getClassMetaForObject(o); 112 } 113 114 String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr(); 115 if (wrapperAttr != null) { 116 out.append('{').cr(indent).attr(wrapperAttr).append(':').s(indent); 117 indent++; 118 } 119 120 // '\0' characters are considered null. 121 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) { 122 out.append("null"); 123 } else if (sType.isNumber() || sType.isBoolean()) { 124 out.append(o); 125 } else if (sType.isBean()) { 126 serializeBeanMap(out, toBeanMap(o), typeName); 127 } else if (sType.isUri() || (pMeta != null && pMeta.isUri())) { 128 out.uriValue(o); 129 } else if (sType.isMap()) { 130 if (o instanceof BeanMap) 131 serializeBeanMap(out, (BeanMap)o, typeName); 132 else 133 serializeMap(out, (Map)o, eType); 134 } else if (sType.isCollection()) { 135 serializeCollection(out, (Collection) o, eType); 136 } else if (sType.isArray()) { 137 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 138 } else if (sType.isReader() || sType.isInputStream()) { 139 IOUtils.pipe(o, out); 140 } else { 141 out.stringValue(toString(o)); 142 } 143 144 if (wrapperAttr != null) { 145 indent--; 146 out.cre(indent-1).append('}'); 147 } 148 149 if (! isRecursion) 150 pop(); 151 return out; 152 } 153 154 @SuppressWarnings({ "rawtypes", "unchecked" }) 155 private SerializerWriter serializeMap(JsonWriter out, Map m, ClassMeta<?> type) throws Exception { 156 157 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 158 159 m = sort(m); 160 161 int i = indent; 162 out.append('{'); 163 164 Iterator mapEntries = m.entrySet().iterator(); 165 166 while (mapEntries.hasNext()) { 167 Map.Entry e = (Map.Entry) mapEntries.next(); 168 Object value = e.getValue(); 169 170 Object key = generalize(e.getKey(), keyType); 171 172 out.cr(i).attr(toString(key)).append(':').s(i); 173 174 serializeAnything(out, value, valueType, (key == null ? null : toString(key)), null); 175 176 if (mapEntries.hasNext()) 177 out.append(',').smi(i); 178 } 179 180 out.cre(i-1).append('}'); 181 182 return out; 183 } 184 185 private SerializerWriter serializeBeanMap(JsonWriter out, BeanMap<?> m, String typeName) throws Exception { 186 int i = indent; 187 out.append('{'); 188 189 boolean addComma = false; 190 for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) { 191 BeanPropertyMeta pMeta = p.getMeta(); 192 if (pMeta.canRead()) { 193 ClassMeta<?> cMeta = p.getClassMeta(); 194 String key = p.getName(); 195 Object value = p.getValue(); 196 Throwable t = p.getThrown(); 197 if (t != null) 198 onBeanGetterException(pMeta, t); 199 200 if (canIgnoreValue(cMeta, key, value)) 201 continue; 202 203 if (addComma) 204 out.append(',').smi(i); 205 206 out.cr(i).attr(key).append(':').s(i); 207 208 serializeAnything(out, value, cMeta, key, pMeta); 209 210 addComma = true; 211 } 212 } 213 out.cre(i-1).append('}'); 214 return out; 215 } 216 217 @SuppressWarnings({"rawtypes", "unchecked"}) 218 private SerializerWriter serializeCollection(JsonWriter out, Collection c, ClassMeta<?> type) throws Exception { 219 220 ClassMeta<?> elementType = type.getElementType(); 221 222 c = sort(c); 223 224 out.append('['); 225 226 for (Iterator i = c.iterator(); i.hasNext();) { 227 Object value = i.next(); 228 out.cr(indent); 229 serializeAnything(out, value, elementType, "<iterator>", null); 230 if (i.hasNext()) 231 out.append(',').smi(indent); 232 } 233 out.cre(indent-1).append(']'); 234 return out; 235 } 236 237 238 /** 239 * Returns the {@link JsonSerializer#JSON_addBeanTypeProperties} setting value for this session. 240 * 241 * @return The {@link JsonSerializer#JSON_addBeanTypeProperties} setting value for this session. 242 */ 243 @Override /* SerializerSession */ 244 protected final boolean isAddBeanTypeProperties() { 245 return addBeanTypeProperties; 246 } 247 248 /** 249 * Converts the specified output target object to an {@link JsonWriter}. 250 * 251 * @param out The output target object. 252 * @return The output target object wrapped in an {@link JsonWriter}. 253 * @throws Exception 254 */ 255 protected final JsonWriter getJsonWriter(SerializerPipe out) throws Exception { 256 Object output = out.getRawOutput(); 257 if (output instanceof JsonWriter) 258 return (JsonWriter)output; 259 JsonWriter w = new JsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), escapeSolidus, getQuoteChar(), 260 simpleMode, isTrimStrings(), getUriResolver()); 261 out.setWriter(w); 262 return w; 263 } 264}