blob: adaf574b3fb54fee6daf068749c2a2ba818e16a5 [file] [log] [blame]
Carsten Ziegeler3314f912014-07-30 07:22:32 +00001/*
2 * Copyright (c) OSGi Alliance (2012, 2014). All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.osgi.dto;
18
19import java.lang.reflect.Array;
20import java.lang.reflect.Field;
21import java.lang.reflect.Modifier;
22import java.util.IdentityHashMap;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26
27/**
28 * Super type for Data Transfer Objects.
29 *
30 * <p>
31 * A Data Transfer Object (DTO) is easily serializable having only public fields
32 * of primitive types and their wrapper classes, Strings, and DTOs. List, Set,
33 * Map and array aggregates may also be used. The aggregates must only hold
34 * objects of the listed types or aggregates.
35 *
36 * <p>
37 * The object graph from a Data Transfer Object must be a tree to simplify
38 * serialization and deserialization.
39 *
40 * @author $Id$
41 * @NotThreadSafe
42 */
43public abstract class DTO {
44
45 /**
46 * Return a string representation of this DTO suitable for use when
47 * debugging.
48 *
49 * <p>
50 * The format of the string representation is not specified and subject to
51 * change.
52 *
53 * @return A string representation of this DTO suitable for use when
54 * debugging.
55 */
56 @Override
57 public String toString() {
58 return appendValue(new StringBuilder(), new IdentityHashMap<Object, String>(), "#", this).toString();
59 }
60
61 /**
62 * Append the specified DTO's string representation to the specified
63 * StringBuilder.
64 *
65 * @param result StringBuilder to which the string representation is
66 * appended.
67 * @param objectRefs References to "seen" objects.
68 * @param refpath The reference path of the specified DTO.
69 * @param dto The DTO whose string representation is to be appended.
70 * @return The specified StringBuilder.
71 */
72 private static StringBuilder appendDTO(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final DTO dto) {
73 result.append("{");
74 String delim = "";
75 for (Field field : dto.getClass().getFields()) {
76 if (Modifier.isStatic(field.getModifiers())) {
77 continue;
78 }
79 result.append(delim);
80 final String name = field.getName();
81 appendString(result, name);
82 result.append(":");
83 Object value = null;
84 try {
85 value = field.get(dto);
86 } catch (IllegalAccessException e) {
87 // use null value;
88 }
89 appendValue(result, objectRefs, refpath + "/" + name, value);
90 delim = ", ";
91 }
92 result.append("}");
93 return result;
94 }
95
96 /**
97 * Append the specified value's string representation to the specified
98 * StringBuilder.
99 *
100 * <p>
101 * This method handles cycles in the object graph, using path-based
102 * references, even though the specification requires the object graph from
103 * a DTO to be a tree.
104 *
105 * @param result StringBuilder to which the string representation is
106 * appended.
107 * @param objectRefs References to "seen" objects.
108 * @param refpath The reference path of the specified value.
109 * @param value The object whose string representation is to be appended.
110 * @return The specified StringBuilder.
111 */
112 private static StringBuilder appendValue(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Object value) {
113 if (value == null) {
114 return result.append("null");
115 }
116 // Simple Java types
117 if (value instanceof String || value instanceof Character) {
118 return appendString(result, compress(value.toString()));
119 }
120 if (value instanceof Number || value instanceof Boolean) {
121 return result.append(value.toString());
122 }
123
124 // Complex types
125 final String path = objectRefs.get(value);
126 if (path != null) {
127 result.append("{\"$ref\":");
128 appendString(result, path);
129 result.append("}");
130 return result;
131 }
132 objectRefs.put(value, refpath);
133
134 if (value instanceof DTO) {
135 return appendDTO(result, objectRefs, refpath, (DTO) value);
136 }
137 if (value instanceof Map) {
138 return appendMap(result, objectRefs, refpath, (Map<?, ?>) value);
139 }
140 if (value instanceof List || value instanceof Set) {
141 return appendIterable(result, objectRefs, refpath, (Iterable<?>) value);
142 }
143 if (value.getClass().isArray()) {
144 return appendArray(result, objectRefs, refpath, value);
145 }
146 return appendString(result, compress(value.toString()));
147 }
148
149 /**
150 * Append the specified array's string representation to the specified
151 * StringBuilder.
152 *
153 * @param result StringBuilder to which the string representation is
154 * appended.
155 * @param objectRefs References to "seen" objects.
156 * @param refpath The reference path of the specified array.
157 * @param array The array whose string representation is to be appended.
158 * @return The specified StringBuilder.
159 */
160 private static StringBuilder appendArray(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Object array) {
161 result.append("[");
162 final int length = Array.getLength(array);
163 for (int i = 0; i < length; i++) {
164 if (i > 0) {
165 result.append(",");
166 }
167 appendValue(result, objectRefs, refpath + "/" + i, Array.get(array, i));
168 }
169 result.append("]");
170 return result;
171 }
172
173 /**
174 * Append the specified iterable's string representation to the specified
175 * StringBuilder.
176 *
177 * @param result StringBuilder to which the string representation is
178 * appended.
179 * @param objectRefs References to "seen" objects.
180 * @param refpath The reference path of the specified list.
181 * @param iterable The iterable whose string representation is to be
182 * appended.
183 * @return The specified StringBuilder.
184 */
185 private static StringBuilder appendIterable(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Iterable<?> iterable) {
186 result.append("[");
187 int i = 0;
188 for (Object item : iterable) {
189 if (i > 0) {
190 result.append(",");
191 }
192 appendValue(result, objectRefs, refpath + "/" + i, item);
193 i++;
194 }
195 result.append("]");
196 return result;
197 }
198
199 /**
200 * Append the specified map's string representation to the specified
201 * StringBuilder.
202 *
203 * @param result StringBuilder to which the string representation is
204 * appended.
205 * @param objectRefs References to "seen" objects.
206 * @param refpath The reference path of the specified map.
207 * @param map The map whose string representation is to be appended.
208 * @return The specified StringBuilder.
209 */
210 private static StringBuilder appendMap(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Map<?, ?> map) {
211 result.append("{");
212 String delim = "";
213 for (Map.Entry<?, ?> entry : map.entrySet()) {
214 result.append(delim);
215 final String name = String.valueOf(entry.getKey());
216 appendString(result, name);
217 result.append(":");
218 final Object value = entry.getValue();
219 appendValue(result, objectRefs, refpath + "/" + name, value);
220 delim = ", ";
221 }
222 result.append("}");
223 return result;
224 }
225
226 /**
227 * Append the specified string to the specified StringBuilder.
228 *
229 * @param result StringBuilder to which the string is appended.
230 * @param string The string to be appended.
231 * @return The specified StringBuilder.
232 */
233 private static StringBuilder appendString(final StringBuilder result, final CharSequence string) {
234 result.append("\"");
235 int i = result.length();
236 result.append(string);
237 while (i < result.length()) { // escape if necessary
238 char c = result.charAt(i);
239 if ((c == '"') || (c == '\\')) {
240 result.insert(i, '\\');
241 i = i + 2;
242 continue;
243 }
244 if (c < 0x20) {
245 result.insert(i + 1, Integer.toHexString(c | 0x10000));
246 result.replace(i, i + 2, "\\u");
247 i = i + 6;
248 continue;
249 }
250 i++;
251 }
252 result.append("\"");
253 return result;
254 }
255
256 /**
257 * Compress, in length, the specified string.
258 *
259 * @param in The string to potentially compress.
260 * @return The string compressed, if necessary.
261 */
262 private static CharSequence compress(final CharSequence in) {
263 final int length = in.length();
264 if (length <= 21) {
265 return in;
266 }
267 StringBuilder result = new StringBuilder(21);
268 result.append(in, 0, 9);
269 result.append("...");
270 result.append(in, length - 9, length);
271 return result;
272 }
273}