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