1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002,2008 Oracle. All rights reserved. 5 * 6 * $Id: CompositeKeyFormat.java,v 1.1 2008/02/07 17:12:27 mark Exp $ 7 */ 8 9package com.sleepycat.persist.impl; 10 11import java.util.ArrayList; 12import java.util.Arrays; 13import java.util.HashMap; 14import java.util.IdentityHashMap; 15import java.util.List; 16import java.util.Map; 17 18import com.sleepycat.persist.model.ClassMetadata; 19import com.sleepycat.persist.model.FieldMetadata; 20import com.sleepycat.persist.raw.RawField; 21import com.sleepycat.persist.raw.RawObject; 22 23/** 24 * Format for a composite key class. 25 * 26 * This class is similar to ComplexFormat in that a composite key class and 27 * other complex classes have fields, and the Accessor interface is used to 28 * access those fields. Composite key classes are different in the following 29 * ways: 30 * 31 * - The superclass must be Object. No inheritance is allowed. 32 * 33 * - All instance fields must be annotated with @KeyField, which determines 34 * their order in the data bytes. 35 * 36 * - Although fields may be reference types (primitive wrappers or other simple 37 * reference types), they are stored as if they were primitives. No object 38 * format ID is stored, and the class of the object must be the declared 39 * classs of the field; i.e., no polymorphism is allowed for key fields. 40 * In other words, a composite key is stored as an ordinary tuple as defined 41 * in the com.sleepycat.bind.tuple package. This keeps the key small and 42 * gives it a well defined sort order. 43 * 44 * - If the key class implements Comparable, it is called by the Database 45 * btree comparator. It must therefore be available during JE recovery, 46 * before the store and catalog have been opened. To support this, this 47 * format can be constructed during recovery. A SimpleCatalog singleton 48 * instance is used to provide a catalog of simple types that is used by 49 * the composite key format. 50 * 51 * - When interacting with the Accessor, the composite key format treats the 52 * Accessor's non-key fields as its key fields. The Accessor's key fields 53 * are secondary keys, while the composite format's key fields are the 54 * component parts of a single key. 55 * 56 * @author Mark Hayes 57 */ 58public class CompositeKeyFormat extends Format { 59 60 private static final long serialVersionUID = 306843428409314630L; 61 62 private ClassMetadata metadata; 63 private List<FieldInfo> fields; 64 private transient Accessor objAccessor; 65 private transient Accessor rawAccessor; 66 private transient volatile Map<String,RawField> rawFields; 67 private transient volatile FieldInfo[] rawInputFields; 68 69 static String[] getFieldNameArray(List<FieldMetadata> list) { 70 int index = 0; 71 String[] a = new String[list.size()]; 72 for (FieldMetadata f : list) { 73 a[index] = f.getName(); 74 index += 1; 75 } 76 return a; 77 } 78 79 CompositeKeyFormat(Class cls, 80 ClassMetadata metadata, 81 List<FieldMetadata> fieldNames) { 82 this(cls, metadata, getFieldNameArray(fieldNames)); 83 } 84 85 CompositeKeyFormat(Class cls, 86 ClassMetadata metadata, 87 String[] fieldNames) { 88 super(cls); 89 this.metadata = metadata; 90 91 /* Check that the superclass is Object. */ 92 Class superCls = cls.getSuperclass(); 93 if (superCls != Object.class) { 94 throw new IllegalArgumentException 95 ("Composite key class must be derived from Object: " + 96 cls.getName()); 97 } 98 99 /* Populate fields list in fieldNames order. */ 100 List<FieldInfo> instanceFields = FieldInfo.getInstanceFields(cls); 101 fields = new ArrayList<FieldInfo>(instanceFields.size()); 102 for (String fieldName : fieldNames) { 103 FieldInfo field = null; 104 for (FieldInfo tryField : instanceFields) { 105 if (fieldName.equals(tryField.getName())) { 106 field = tryField; 107 break; 108 } 109 } 110 if (field == null) { 111 throw new IllegalArgumentException 112 ("Composite key field is not an instance field:" + 113 getClassName() + '.' + fieldName); 114 } 115 fields.add(field); 116 instanceFields.remove(field); 117 if (!SimpleCatalog.isSimpleType(field.getFieldClass())) { 118 throw new IllegalArgumentException 119 ("Composite key field is not a simple type: " + 120 getClassName() + '.' + fieldName); 121 } 122 } 123 if (instanceFields.size() > 0) { 124 throw new IllegalArgumentException 125 ("All composite key instance fields must be key fields: " + 126 getClassName() + '.' + instanceFields.get(0).getName()); 127 } 128 } 129 130 @Override 131 void migrateFromBeta(Map<String,Format> formatMap) { 132 super.migrateFromBeta(formatMap); 133 for (FieldInfo field : fields) { 134 field.migrateFromBeta(formatMap); 135 } 136 } 137 138 @Override 139 boolean isModelClass() { 140 return true; 141 } 142 143 @Override 144 ClassMetadata getClassMetadata() { 145 if (metadata == null) { 146 throw new IllegalStateException(getClassName()); 147 } 148 return metadata; 149 } 150 151 @Override 152 public Map<String,RawField> getFields() { 153 154 /* 155 * Lazily create the raw type information. Synchronization is not 156 * required since this object is immutable. If by chance we create two 157 * maps when two threads execute this block, no harm is done. But be 158 * sure to assign the rawFields field only after the map is fully 159 * populated. 160 */ 161 if (rawFields == null) { 162 Map<String,RawField> map = new HashMap<String,RawField>(); 163 for (RawField field : fields) { 164 map.put(field.getName(), field); 165 } 166 rawFields = map; 167 } 168 return rawFields; 169 } 170 171 @Override 172 void collectRelatedFormats(Catalog catalog, 173 Map<String,Format> newFormats) { 174 /* Collect field formats. */ 175 for (FieldInfo field : fields) { 176 field.collectRelatedFormats(catalog, newFormats); 177 } 178 } 179 180 @Override 181 void initialize(Catalog catalog, int initVersion) { 182 /* Initialize all fields. */ 183 for (FieldInfo field : fields) { 184 field.initialize(catalog, initVersion); 185 } 186 /* Create the accessor. */ 187 Class type = getType(); 188 if (type != null) { 189 if (EnhancedAccessor.isEnhanced(type)) { 190 objAccessor = new EnhancedAccessor(type); 191 } else { 192 objAccessor = new ReflectionAccessor(catalog, type, fields); 193 } 194 } 195 rawAccessor = new RawAccessor(this, fields); 196 } 197 198 @Override 199 Object newArray(int len) { 200 return objAccessor.newArray(len); 201 } 202 203 @Override 204 public Object newInstance(EntityInput input, boolean rawAccess) { 205 Accessor accessor = rawAccess ? rawAccessor : objAccessor; 206 return accessor.newInstance(); 207 } 208 209 @Override 210 public Object readObject(Object o, EntityInput input, boolean rawAccess) { 211 Accessor accessor = rawAccess ? rawAccessor : objAccessor; 212 accessor.readNonKeyFields(o, input, 0, Accessor.MAX_FIELD_NUM, -1); 213 return o; 214 } 215 216 @Override 217 void writeObject(Object o, EntityOutput output, boolean rawAccess) { 218 Accessor accessor = rawAccess ? rawAccessor : objAccessor; 219 accessor.writeNonKeyFields(o, output); 220 } 221 222 @Override 223 Object convertRawObject(Catalog catalog, 224 boolean rawAccess, 225 RawObject rawObject, 226 IdentityHashMap converted) { 227 228 /* 229 * Synchronization is not required since rawInputFields is immutable. 230 * If by chance we create duplicate values when two threads execute 231 * this block, no harm is done. But be sure to assign the field only 232 * after the values are fully populated. 233 */ 234 FieldInfo[] myFields = rawInputFields; 235 if (myFields == null) { 236 myFields = new FieldInfo[fields.size()]; 237 fields.toArray(myFields); 238 rawInputFields = myFields; 239 } 240 if (rawObject.getSuper() != null) { 241 throw new IllegalArgumentException 242 ("RawObject has too many superclasses: " + 243 rawObject.getType().getClassName()); 244 } 245 RawObject[] objects = new RawObject[myFields.length]; 246 Arrays.fill(objects, rawObject); 247 EntityInput in = new RawComplexInput 248 (catalog, rawAccess, converted, myFields, objects); 249 Object o = newInstance(in, rawAccess); 250 converted.put(rawObject, o); 251 return readObject(o, in, rawAccess); 252 } 253 254 @Override 255 void skipContents(RecordInput input) { 256 int maxNum = fields.size(); 257 for (int i = 0; i < maxNum; i += 1) { 258 fields.get(i).getType().skipContents(input); 259 } 260 } 261 262 @Override 263 void copySecKey(RecordInput input, RecordOutput output) { 264 int maxNum = fields.size(); 265 for (int i = 0; i < maxNum; i += 1) { 266 fields.get(i).getType().copySecKey(input, output); 267 } 268 } 269 270 @Override 271 Format getSequenceKeyFormat() { 272 if (fields.size() != 1) { 273 throw new IllegalArgumentException 274 ("A composite key class used with a sequence may contain " + 275 "only a single integer key field: " + getClassName()); 276 } 277 return fields.get(0).getType().getSequenceKeyFormat(); 278 } 279 280 @Override 281 boolean evolve(Format newFormatParam, Evolver evolver) { 282 283 /* Disallow evolution to a non-composite format. */ 284 if (!(newFormatParam instanceof CompositeKeyFormat)) { 285 evolver.addEvolveError 286 (this, newFormatParam, null, 287 "A composite key class may not be changed to a different " + 288 "type"); 289 return false; 290 } 291 CompositeKeyFormat newFormat = (CompositeKeyFormat) newFormatParam; 292 293 /* Check for added or removed key fields. */ 294 if (fields.size() != newFormat.fields.size()) { 295 evolver.addEvolveError 296 (this, newFormat, 297 "Composite key class fields were added or removed ", 298 "Old fields: " + fields + 299 " new fields: " + newFormat.fields); 300 return false; 301 } 302 303 /* Check for modified key fields. */ 304 boolean newVersion = false; 305 for (int i = 0; i < fields.size(); i += 1) { 306 int result = evolver.evolveRequiredKeyField 307 (this, newFormat, fields.get(i), 308 newFormat.fields.get(i)); 309 if (result == Evolver.EVOLVE_FAILURE) { 310 return false; 311 } 312 if (result == Evolver.EVOLVE_NEEDED) { 313 newVersion = true; 314 } 315 } 316 317 /* 318 * We never need to use a custom reader because the physical key field 319 * formats never change. But we do create a new evolved format when 320 * a type changes (primitive <-> primitive wrapper) so that the new 321 * type information is correct. 322 */ 323 if (newVersion) { 324 evolver.useEvolvedFormat(this, newFormat, newFormat); 325 } else { 326 evolver.useOldFormat(this, newFormat); 327 } 328 return true; 329 } 330} 331