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.impl; 9 10 import java.io.Serializable; 11 import java.lang.reflect.Field; 12 import java.lang.reflect.Modifier; 13 import java.lang.reflect.ParameterizedType; 14 import java.lang.reflect.Type; 15 import java.util.ArrayList; 16 import java.util.Collection; 17 import java.util.List; 18 import java.util.Map; 19 20 import com.sleepycat.compat.DbCompat; 21 import com.sleepycat.persist.model.EntityModel; 22 import com.sleepycat.persist.raw.RawField; 23 import com.sleepycat.persist.model.FieldMetadata; 24 import com.sleepycat.persist.model.ClassMetadata; 25 26 /** 27 * A field definition used by ComplexFormat and CompositeKeyFormat. 28 * 29 * <p>Note that the equals(), compareTo() and hashCode() methods only use the 30 * name field in this class. Comparing two FieldInfo objects is only done when 31 * both are declared in the same class, so comparing the field name is 32 * sufficient.</p> 33 * 34 * @author Mark Hayes 35 */ 36 class FieldInfo implements RawField, Serializable, Comparable<FieldInfo> { 37 38 private static final long serialVersionUID = 2062721100372306296L; 39 40 /** 41 * Returns a list of all non-transient non-static fields that are declared 42 * in the given class. 43 */ getInstanceFields(Class cls, ClassMetadata clsMeta)44 static List<FieldInfo> getInstanceFields(Class cls, 45 ClassMetadata clsMeta) { 46 List<FieldInfo> fields = null; 47 if (clsMeta != null) { 48 Collection<FieldMetadata> persistentFields = 49 clsMeta.getPersistentFields(); 50 if (persistentFields != null) { 51 fields = new ArrayList<FieldInfo>(persistentFields.size()); 52 String clsName = cls.getName(); 53 for (FieldMetadata fieldMeta : persistentFields) { 54 if (!clsName.equals(fieldMeta.getDeclaringClassName())) { 55 throw new IllegalArgumentException 56 ("Persistent field " + fieldMeta + 57 " must be declared in " + clsName); 58 } 59 Field field; 60 try { 61 field = cls.getDeclaredField(fieldMeta.getName()); 62 } catch (NoSuchFieldException e) { 63 throw new IllegalArgumentException 64 ("Persistent field " + fieldMeta + 65 " is not declared in this class"); 66 } 67 if (!field.getType().getName().equals 68 (fieldMeta.getClassName())) { 69 throw new IllegalArgumentException 70 ("Persistent field " + fieldMeta + 71 " must be of type " + field.getType().getName()); 72 } 73 if (Modifier.isStatic(field.getModifiers())) { 74 throw new IllegalArgumentException 75 ("Persistent field " + fieldMeta + 76 " may not be static"); 77 } 78 fields.add(new FieldInfo(field)); 79 } 80 } 81 } 82 if (fields == null) { 83 Field[] declaredFields = cls.getDeclaredFields(); 84 fields = new ArrayList<FieldInfo>(declaredFields.length); 85 for (Field field : declaredFields) { 86 int mods = field.getModifiers(); 87 if (!Modifier.isTransient(mods) && !Modifier.isStatic(mods)) { 88 fields.add(new FieldInfo(field)); 89 } 90 } 91 } 92 return fields; 93 } 94 getField(List<FieldInfo> fields, String fieldName)95 static FieldInfo getField(List<FieldInfo> fields, String fieldName) { 96 int i = getFieldIndex(fields, fieldName); 97 if (i >= 0) { 98 return fields.get(i); 99 } else { 100 return null; 101 } 102 } 103 getFieldIndex(List<FieldInfo> fields, String fieldName)104 static int getFieldIndex(List<FieldInfo> fields, String fieldName) { 105 for (int i = 0; i < fields.size(); i += 1) { 106 FieldInfo field = fields.get(i); 107 if (fieldName.equals(field.getName())) { 108 return i; 109 } 110 } 111 return -1; 112 } 113 114 private String name; 115 private String className; 116 private Format format; 117 private transient Class cls; 118 private transient Field field; 119 FieldInfo(Field field)120 private FieldInfo(Field field) { 121 name = field.getName(); 122 cls = field.getType(); 123 className = cls.getName(); 124 this.field = field; 125 } 126 collectRelatedFormats(Catalog catalog, Map<String, Format> newFormats)127 void collectRelatedFormats(Catalog catalog, 128 Map<String, Format> newFormats) { 129 130 /* 131 * Prior to intialization we save the newly created format in the 132 * format field so that it can be used by class evolution. But note 133 * that it may be replaced by the initialize method. [#16233] 134 */ 135 format = catalog.createFormat(cls, newFormats); 136 137 /* 138 * If the created format is a NonPersistentFormat, and the field is a 139 * map or a collection, then the generic types of this field are 140 * ParameterizedTypes, e.g., Map<MyClass1, MyClass2>, so the formats of 141 * the generic types for this field, i.e., MyClass1 and MyClass2 will 142 * be created here. [#19377] 143 */ 144 Class cls = field.getType(); 145 if (format instanceof NonPersistentFormat && 146 (java.util.Map.class.isAssignableFrom(cls) || 147 java.util.Collection.class.isAssignableFrom(cls))) { 148 if (field != null && 149 field.getGenericType() instanceof ParameterizedType) { 150 collectParameterizedTypeFormats(catalog, newFormats, 151 (ParameterizedType)field.getGenericType()); 152 } 153 } 154 } 155 156 /* 157 * Create formats for the parameterized types, e.g., will create formats 158 * for MyClass1 and MyClass2 when meeting Map<MyClass1, Set<MyClass2>>, 159 * where MyClass1 and MyClass2 are instance of java.lang.Class. 160 */ 161 private void collectParameterizedTypeFormats(Catalog catalog, Map<String, Format> newFormats, ParameterizedType parameType)162 collectParameterizedTypeFormats(Catalog catalog, 163 Map<String, Format> newFormats, 164 ParameterizedType parameType) { 165 Type[] types = parameType.getActualTypeArguments(); 166 for (int i = 0; i < types.length; i++) { 167 if (types[i] instanceof ParameterizedType) { 168 collectParameterizedTypeFormats(catalog, newFormats, 169 (ParameterizedType)types[i]); 170 } else if (types[i] instanceof Class) { 171 172 /* 173 * Only use Catalog.createFormat to create the format for the 174 * class which is instance of java.lang.class. 175 */ 176 catalog.createFormat((Class)types[i], newFormats); 177 } 178 } 179 } 180 migrateFromBeta(Map<String, Format> formatMap)181 void migrateFromBeta(Map<String, Format> formatMap) { 182 if (format == null) { 183 format = formatMap.get(className); 184 if (format == null) { 185 throw DbCompat.unexpectedState(className); 186 } 187 } 188 } 189 initialize(Catalog catalog, EntityModel model, int initVersion)190 void initialize(Catalog catalog, EntityModel model, int initVersion) { 191 192 /* 193 * Reset the format if it was never initialized, which can occur when a 194 * new format instance created during class evolution and discarded 195 * because nothing changed. [#16233] 196 * 197 * Note that the format field may be null when used in a composite key 198 * format used as a key comparator (via PersistComparator). In that 199 * case (null format), we must not attempt to reset the format. 200 */ 201 if (format != null && format.isNew()) { 202 format = catalog.getFormat(className); 203 } 204 } 205 getFieldClass(Catalog catalog)206 Class getFieldClass(Catalog catalog) { 207 if (cls == null) { 208 try { 209 cls = catalog.resolveClass(className); 210 } catch (ClassNotFoundException e) { 211 throw DbCompat.unexpectedException(e); 212 } 213 } 214 return cls; 215 } 216 getClassName()217 String getClassName() { 218 return className; 219 } 220 getName()221 public String getName() { 222 return name; 223 } 224 getType()225 public Format getType() { 226 return format; 227 } 228 compareTo(FieldInfo o)229 public int compareTo(FieldInfo o) { 230 return name.compareTo(o.name); 231 } 232 233 @Override equals(Object other)234 public boolean equals(Object other) { 235 if (other instanceof FieldInfo) { 236 FieldInfo o = (FieldInfo) other; 237 return name.equals(o.name); 238 } else { 239 return false; 240 } 241 } 242 243 @Override hashCode()244 public int hashCode() { 245 return name.hashCode(); 246 } 247 248 @Override toString()249 public String toString() { 250 return "[Field name: " + name + " class: " + className + ']'; 251 } 252 } 253