1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: RawObject.java,v 1.1 2008/02/07 17:12:28 mark Exp $
7 */
8
9package com.sleepycat.persist.raw;
10
11import java.util.Arrays;
12import java.util.Map;
13import java.util.TreeSet;
14
15import com.sleepycat.persist.evolve.Conversion;
16import com.sleepycat.persist.model.EntityModel;
17
18/**
19 * A raw instance that can be used with a {@link RawStore} or {@link
20 * Conversion}.  A <code>RawObject</code> is used to represent instances of
21 * complex types (persistent classes with fields), arrays, and enum values.  It
22 * is not used to represent non-enum simple types, which are represented as
23 * simple objects.  This includes primitives, which are represented as simple
24 * objects using their wrapper class.
25 *
26 * <p>{@code RawObject} objects are thread-safe.  Multiple threads may safely
27 * call the methods of a shared {@code RawObject} object.</p>
28 *
29 * @author Mark Hayes
30 */
31public class RawObject {
32
33    private static final String INDENT = "  ";
34
35    private RawType type;
36    private Map<String,Object> values;
37    private Object[] elements;
38    private String enumConstant;
39    private RawObject superObject;
40
41    /**
42     * Creates a raw object with a given set of field values for a complex
43     * type.
44     *
45     * @param type the type of this raw object.
46     *
47     * @param values a map of field name to value for each declared field in
48     * the class, or null to create an empty map.  Each value in the map is a
49     * {@link RawObject}, a {@link <a
50     * href="../model/Entity.html#simpleTypes">simple type</a>} instance, or
51     * null.
52     *
53     * @param superObject the instance of the superclass, or null if the
54     * superclass is {@code Object}.
55     *
56     * @throws IllegalArgumentException if the type argument is an array type.
57     */
58    public RawObject(RawType type,
59                     Map<String,Object> values,
60                     RawObject superObject) {
61        if (type == null || values == null) {
62            throw new NullPointerException();
63        }
64        this.type = type;
65        this.values = values;
66        this.superObject = superObject;
67    }
68
69    /**
70     * Creates a raw object with the given array elements for an array type.
71     *
72     * @param type the type of this raw object.
73     *
74     * @param elements an array of elements.  Each element in the array is a
75     * {@link RawObject}, a {@link <a
76     * href="../model/Entity.html#simpleTypes">simple type</a>} instance, or
77     * null.
78     *
79     * @throws IllegalArgumentException if the type argument is not an array
80     * type.
81     */
82    public RawObject(RawType type, Object[] elements) {
83        if (type == null || elements == null) {
84            throw new NullPointerException();
85        }
86        this.type = type;
87        this.elements = elements;
88    }
89
90    /**
91     * Creates a raw object with the given enum value for an enum type.
92     *
93     * @param type the type of this raw object.
94     *
95     * @param enumConstant the String value of this enum constant; must be
96     * one of the Strings returned by {@link RawType#getEnumConstants}.
97     *
98     * @throws IllegalArgumentException if the type argument is not an array
99     * type.
100     */
101    public RawObject(RawType type, String enumConstant) {
102        if (type == null || enumConstant == null) {
103            throw new NullPointerException();
104        }
105        this.type = type;
106        this.enumConstant = enumConstant;
107    }
108
109    /**
110     * Returns the raw type information for this raw object.
111     *
112     * <p>Note that if this object is unevolved, the returned type may be
113     * different from the current type returned by {@link
114     * EntityModel#getRawType EntityModel.getRawType} for the same class name.
115     * This can only occur in a {@link Conversion#convert
116     * Conversion.convert}.</p>
117     */
118    public RawType getType() {
119        return type;
120    }
121
122    /**
123     * Returns a map of field name to value for a complex type, or null for an
124     * array type or an enum type.  The map contains a String key for each
125     * declared field in the class.  Each value in the map is a {@link
126     * RawObject}, a {@link <a href="../model/Entity.html#simpleTypes">simple
127     * type</a>} instance, or null.
128     *
129     * <p>There will be an entry in the map for every field declared in this
130     * type, as determined by {@link RawType#getFields} for the type returned
131     * by {@link #getType}.  Values in the map may be null for fields with
132     * non-primitive types.</p>
133     */
134    public Map<String,Object> getValues() {
135        return values;
136    }
137
138    /**
139     * Returns the array of elements for an array type, or null for a complex
140     * type or an enum type.  Each element in the array is a {@link RawObject},
141     * a {@link <a href="../model/Entity.html#simpleTypes">simple type</a>}
142     * instance, or null.
143     */
144    public Object[] getElements() {
145        return elements;
146    }
147
148    /**
149     * Returns the enum constant String for an enum type, or null for a complex
150     * type or an array type.  The String returned will be one of the Strings
151     * returned by {@link RawType#getEnumConstants}.
152     */
153    public String getEnum() {
154        return enumConstant;
155    }
156
157    /**
158     * Returns the instance of the superclass, or null if the superclass is
159     * {@code Object} or {@code Enum}.
160     */
161    public RawObject getSuper() {
162        return superObject;
163    }
164
165    @Override
166    public boolean equals(Object other) {
167        if (other == this) {
168            return true;
169        }
170        if (!(other instanceof RawObject)) {
171            return false;
172        }
173        RawObject o = (RawObject) other;
174        if (type != o.type) {
175            return false;
176        }
177        if (!Arrays.deepEquals(elements, o.elements)) {
178            return false;
179        }
180        if (values != null) {
181            if (!values.equals(o.values)) {
182                return false;
183            }
184        } else {
185            if (o.values != null) {
186                return false;
187            }
188        }
189        if (superObject != null) {
190            if (!superObject.equals(o.superObject)) {
191                return false;
192            }
193        } else {
194            if (o.superObject != null) {
195                return false;
196            }
197        }
198        return true;
199    }
200
201    @Override
202    public int hashCode() {
203        return System.identityHashCode(type) +
204               Arrays.deepHashCode(elements) +
205               (values != null ? values.hashCode() : 0) +
206               (superObject != null ? superObject.hashCode() : 0);
207    }
208
209    @Override
210    public String toString() {
211        StringBuffer buf = new StringBuffer(500);
212        formatRawObject(buf, "", null, false);
213        return buf.toString();
214    }
215
216    private void formatRawObject(StringBuffer buf,
217                                 String indent,
218                                 String id,
219                                 boolean isSuper) {
220        String indent2 = indent + INDENT;
221        String endTag;
222        buf.append(indent);
223        if (type.isArray()) {
224            buf.append("<Array");
225            endTag = "</Array>";
226        } else if (type.isEnum()) {
227            buf.append("<Enum");
228            endTag = "</Enum>";
229        } else if (isSuper) {
230            buf.append("<Super");
231            endTag = "</Super>";
232        } else {
233            buf.append("<Object");
234            endTag = "</Object>";
235        }
236        if (id != null) {
237            formatId(buf, id);
238        }
239        if (type.isArray()) {
240            buf.append(" length=\"");
241            buf.append(elements.length);
242            buf.append('"');
243        }
244        buf.append(" class=\"");
245        buf.append(type.getClassName());
246        buf.append("\">\n");
247
248        if (superObject != null) {
249            superObject.formatRawObject(buf, indent2, null, true);
250        }
251        if (type.isArray()) {
252            for (int i = 0; i < elements.length; i += 1) {
253                formatValue(buf, indent2, String.valueOf(i), elements[i]);
254            }
255        } else if (type.isEnum()) {
256            buf.append(enumConstant);
257        } else {
258            TreeSet<String> keys = new TreeSet<String>(values.keySet());
259            for (String name : keys) {
260                formatValue(buf, indent2, name, values.get(name));
261            }
262        }
263        buf.append(indent);
264        buf.append(endTag);
265        buf.append("\n");
266    }
267
268    private static void formatValue(StringBuffer buf,
269                                    String indent,
270                                    String id,
271                                    Object val) {
272        if (val == null) {
273            buf.append(indent);
274            buf.append("<Null");
275            formatId(buf, id);
276            buf.append("/>\n");
277        } else if (val instanceof RawObject) {
278            ((RawObject) val).formatRawObject(buf, indent, id, false);
279        } else {
280            buf.append(indent);
281            buf.append("<Value");
282            formatId(buf, id);
283            buf.append(" class=\"");
284            buf.append(val.getClass().getName());
285            buf.append("\">");
286            buf.append(val.toString());
287            buf.append("</Value>\n");
288        }
289    }
290
291    private static void formatId(StringBuffer buf, String id) {
292        if (Character.isDigit(id.charAt(0))) {
293            buf.append(" index=\"");
294        } else {
295            buf.append(" field=\"");
296        }
297        buf.append(id);
298        buf.append('"');
299    }
300}
301