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