Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 53074a69bfb3ea8852fa1eb03b219e89999f8b89 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*
 * Copyright (c) OSGi Alliance (2012, 2017). All Rights Reserved.
 * 
 * Licensed 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.osgi.dto;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Super type for Data Transfer Objects.
 * <p>
 * A Data Transfer Object (DTO) is easily serializable having only public fields
 * of primitive types and their wrapper classes, Strings, enums, Version, and
 * DTOs. List, Set, Map, and array aggregates may also be used. The aggregates
 * must only hold objects of the listed types or aggregates.
 * <p>
 * The object graph from a Data Transfer Object must be a tree to simplify
 * serialization and deserialization.
 * 
 * @author $Id$
 * @NotThreadSafe
 */
public abstract class DTO {

    /**
     * Return a string representation of this DTO suitable for use when
     * debugging.
     * 
     * <p>
     * The format of the string representation is not specified and subject to
     * change.
     * 
     * @return A string representation of this DTO suitable for use when
     *         debugging.
     */
    @Override
    public String toString() {
        return appendValue(new StringBuilder(), new IdentityHashMap<Object, String>(), "#", this).toString();
    }

    /**
     * Append the specified DTO's string representation to the specified
     * StringBuilder.
     * 
     * @param result StringBuilder to which the string representation is
     *        appended.
     * @param objectRefs References to "seen" objects.
     * @param refpath The reference path of the specified DTO.
     * @param dto The DTO whose string representation is to be appended.
     * @return The specified StringBuilder.
     */
    private static StringBuilder appendDTO(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final DTO dto) {
        result.append("{");
        String delim = "";
        for (Field field : dto.getClass().getFields()) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            result.append(delim);
            final String name = field.getName();
            appendString(result, name);
            result.append(":");
            Object value = null;
            try {
                value = field.get(dto);
            } catch (IllegalAccessException e) {
                // use null value;
            }
            appendValue(result, objectRefs, refpath + "/" + name, value);
            delim = ", ";
        }
        result.append("}");
        return result;
    }

    /**
     * Append the specified value's string representation to the specified
     * StringBuilder.
     * 
     * <p>
     * This method handles cycles in the object graph, using path-based
     * references, even though the specification requires the object graph from
     * a DTO to be a tree.
     * 
     * @param result StringBuilder to which the string representation is
     *        appended.
     * @param objectRefs References to "seen" objects.
     * @param refpath The reference path of the specified value.
     * @param value The object whose string representation is to be appended.
     * @return The specified StringBuilder.
     */
    private static StringBuilder appendValue(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Object value) {
        if (value == null) {
            return result.append("null");
        }
        // Simple Java types
        if (value instanceof String || value instanceof Character) {
            return appendString(result, compress(value.toString()));
        }
        if (value instanceof Number || value instanceof Boolean) {
            return result.append(value.toString());
        }
		if (value instanceof Enum) {
			return appendString(result, ((Enum< ? >) value).name());
		}
		if ("org.osgi.framework.Version".equals(value.getClass().getName())) {
			return appendString(result, value.toString());
		}

        // Complex types
        final String path = objectRefs.get(value);
        if (path != null) {
            result.append("{\"$ref\":");
            appendString(result, path);
            result.append("}");
            return result;
        }
        objectRefs.put(value, refpath);

        if (value instanceof DTO) {
            return appendDTO(result, objectRefs, refpath, (DTO) value);
        }
        if (value instanceof Map) {
            return appendMap(result, objectRefs, refpath, (Map<?, ?>) value);
        }
        if (value instanceof List || value instanceof Set) {
            return appendIterable(result, objectRefs, refpath, (Iterable<?>) value);
        }
        if (value.getClass().isArray()) {
            return appendArray(result, objectRefs, refpath, value);
        }
        return appendString(result, compress(value.toString()));
    }

    /**
     * Append the specified array's string representation to the specified
     * StringBuilder.
     * 
     * @param result StringBuilder to which the string representation is
     *        appended.
     * @param objectRefs References to "seen" objects.
     * @param refpath The reference path of the specified array.
     * @param array The array whose string representation is to be appended.
     * @return The specified StringBuilder.
     */
    private static StringBuilder appendArray(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Object array) {
        result.append("[");
        final int length = Array.getLength(array);
        for (int i = 0; i < length; i++) {
            if (i > 0) {
                result.append(",");
            }
            appendValue(result, objectRefs, refpath + "/" + i, Array.get(array, i));
        }
        result.append("]");
        return result;
    }

    /**
     * Append the specified iterable's string representation to the specified
     * StringBuilder.
     * 
     * @param result StringBuilder to which the string representation is
     *        appended.
     * @param objectRefs References to "seen" objects.
     * @param refpath The reference path of the specified list.
     * @param iterable The iterable whose string representation is to be
     *        appended.
     * @return The specified StringBuilder.
     */
    private static StringBuilder appendIterable(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Iterable<?> iterable) {
        result.append("[");
        int i = 0;
        for (Object item : iterable) {
            if (i > 0) {
                result.append(",");
            }
            appendValue(result, objectRefs, refpath + "/" + i, item);
            i++;
        }
        result.append("]");
        return result;
    }

    /**
     * Append the specified map's string representation to the specified
     * StringBuilder.
     * 
     * @param result StringBuilder to which the string representation is
     *        appended.
     * @param objectRefs References to "seen" objects.
     * @param refpath The reference path of the specified map.
     * @param map The map whose string representation is to be appended.
     * @return The specified StringBuilder.
     */
    private static StringBuilder appendMap(final StringBuilder result, final Map<Object, String> objectRefs, final String refpath, final Map<?, ?> map) {
        result.append("{");
        String delim = "";
        for (Map.Entry<?, ?> entry : map.entrySet()) {
            result.append(delim);
            final String name = String.valueOf(entry.getKey());
            appendString(result, name);
            result.append(":");
            final Object value = entry.getValue();
            appendValue(result, objectRefs, refpath + "/" + name, value);
            delim = ", ";
        }
        result.append("}");
        return result;
    }

    /**
     * Append the specified string to the specified StringBuilder.
     * 
     * @param result StringBuilder to which the string is appended.
     * @param string The string to be appended.
     * @return The specified StringBuilder.
     */
    private static StringBuilder appendString(final StringBuilder result, final CharSequence string) {
        result.append("\"");
        int i = result.length();
        result.append(string);
        while (i < result.length()) { // escape if necessary
            char c = result.charAt(i);
            if ((c == '"') || (c == '\\')) {
                result.insert(i, '\\');
                i = i + 2;
                continue;
            }
            if (c < 0x20) {
                result.insert(i + 1, Integer.toHexString(c | 0x10000));
                result.replace(i, i + 2, "\\u");
                i = i + 6;
                continue;
            }
            i++;
        }
        result.append("\"");
        return result;
    }

    /**
     * Compress, in length, the specified string.
     * 
     * @param in The string to potentially compress.
     * @return The string compressed, if necessary.
     */
    private static CharSequence compress(final CharSequence in) {
        final int length = in.length();
        if (length <= 21) {
            return in;
        }
        StringBuilder result = new StringBuilder(21);
        result.append(in, 0, 9);
        result.append("...");
        result.append(in, length - 9, length);
        return result;
    }
}

Back to the top