1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: ReflectionAccessor.java,v 1.1 2008/02/07 17:12:27 mark Exp $
7 */
8
9package com.sleepycat.persist.impl;
10
11import java.lang.reflect.AccessibleObject;
12import java.lang.reflect.Array;
13import java.lang.reflect.Constructor;
14import java.lang.reflect.Field;
15import java.lang.reflect.InvocationTargetException;
16import java.lang.reflect.Modifier;
17import java.util.List;
18
19/**
20 * Implements Accessor using reflection.
21 *
22 * @author Mark Hayes
23 */
24class ReflectionAccessor implements Accessor {
25
26    private static final FieldAccess[] EMPTY_KEYS = {};
27
28    private Class type;
29    private Accessor superAccessor;
30    private Constructor constructor;
31    private FieldAccess priKey;
32    private FieldAccess[] secKeys;
33    private FieldAccess[] nonKeys;
34
35    private ReflectionAccessor(Class type, Accessor superAccessor) {
36        this.type = type;
37        this.superAccessor = superAccessor;
38        try {
39            constructor = type.getDeclaredConstructor();
40        } catch (NoSuchMethodException e) {
41            throw new IllegalStateException(type.getName());
42        }
43        if (!Modifier.isPublic(constructor.getModifiers())) {
44            setAccessible(constructor, type.getName() + "()");
45        }
46    }
47
48    ReflectionAccessor(Catalog catalog,
49                       Class type,
50                       Accessor superAccessor,
51                       FieldInfo priKeyField,
52                       List<FieldInfo> secKeyFields,
53                       List<FieldInfo> nonKeyFields) {
54        this(type, superAccessor);
55        if (priKeyField != null) {
56            priKey = getField(catalog, priKeyField, true, false);
57        } else {
58            priKey = null;
59        }
60        if (secKeyFields.size() > 0) {
61            secKeys = getFields(catalog, secKeyFields, false, false);
62        } else {
63            secKeys = EMPTY_KEYS;
64        }
65        if (nonKeyFields.size() > 0) {
66            nonKeys = getFields(catalog, nonKeyFields, false, false);
67        } else {
68            nonKeys = EMPTY_KEYS;
69        }
70    }
71
72    ReflectionAccessor(Catalog catalog,
73                       Class type,
74                       List<FieldInfo> fieldInfos) {
75        this(type, null);
76        priKey = null;
77        secKeys = EMPTY_KEYS;
78        nonKeys = getFields(catalog, fieldInfos, true, true);
79    }
80
81    private FieldAccess[] getFields(Catalog catalog,
82                                    List<FieldInfo> fieldInfos,
83                                    boolean isRequiredKeyField,
84                                    boolean isCompositeKey) {
85        int index = 0;
86        FieldAccess[] fields = new FieldAccess[fieldInfos.size()];
87        for (FieldInfo info : fieldInfos) {
88            fields[index] = getField
89                (catalog, info, isRequiredKeyField, isCompositeKey);
90            index += 1;
91        }
92        return fields;
93    }
94
95    private FieldAccess getField(Catalog catalog,
96                                 FieldInfo fieldInfo,
97                                 boolean isRequiredKeyField,
98                                 boolean isCompositeKey) {
99        Field field;
100        try {
101            field = type.getDeclaredField(fieldInfo.getName());
102        } catch (NoSuchFieldException e) {
103            throw new IllegalStateException(e);
104        }
105        if (!Modifier.isPublic(field.getModifiers())) {
106            setAccessible(field, field.getName());
107        }
108        Class fieldCls = field.getType();
109        if (fieldCls.isPrimitive()) {
110            assert SimpleCatalog.isSimpleType(fieldCls);
111            return new PrimitiveAccess
112                (field, SimpleCatalog.getSimpleFormat(fieldCls));
113        } else if (isRequiredKeyField) {
114            Format format = catalog.getFormat(fieldCls);
115            if (isCompositeKey && !SimpleCatalog.isSimpleType(fieldCls)) {
116                throw new IllegalArgumentException
117                    ("Composite key class has non-simple type field: " +
118                     type.getName() + '.' + field.getName());
119            }
120            return new KeyObjectAccess(field, format);
121        } else {
122            return new ObjectAccess(field);
123        }
124    }
125
126    private void setAccessible(AccessibleObject object, String memberName) {
127        try {
128            object.setAccessible(true);
129        } catch (SecurityException e) {
130            throw new IllegalStateException
131                ("Unable to access non-public member: " +
132                 type.getName() + '.' + memberName +
133                 ". Please configure the Java Security Manager setting: " +
134                 " ReflectPermission suppressAccessChecks", e);
135        }
136    }
137
138    public Object newInstance() {
139        try {
140            return constructor.newInstance();
141        } catch (IllegalAccessException e) {
142            throw new IllegalStateException(e);
143        } catch (InstantiationException e) {
144            throw new IllegalStateException(e);
145        } catch (InvocationTargetException e) {
146            throw new IllegalStateException(e);
147        }
148    }
149
150    public Object newArray(int len) {
151        return Array.newInstance(type, len);
152    }
153
154    public boolean isPriKeyFieldNullOrZero(Object o) {
155        try {
156            if (priKey != null) {
157                return priKey.isNullOrZero(o);
158            } else if (superAccessor != null) {
159                return superAccessor.isPriKeyFieldNullOrZero(o);
160            } else {
161                throw new IllegalStateException("No primary key field");
162            }
163        } catch (IllegalAccessException e) {
164            throw new IllegalStateException(e);
165        }
166    }
167
168    public void writePriKeyField(Object o, EntityOutput output) {
169        try {
170            if (priKey != null) {
171                priKey.write(o, output);
172            } else if (superAccessor != null) {
173                superAccessor.writePriKeyField(o, output);
174            } else {
175                throw new IllegalStateException("No primary key field");
176            }
177        } catch (IllegalAccessException e) {
178            throw new IllegalStateException(e);
179        }
180    }
181
182    public void readPriKeyField(Object o, EntityInput input) {
183        try {
184            if (priKey != null) {
185                priKey.read(o, input);
186            } else if (superAccessor != null) {
187                superAccessor.readPriKeyField(o, input);
188            } else {
189                throw new IllegalStateException("No primary key field");
190            }
191        } catch (IllegalAccessException e) {
192            throw new IllegalStateException(e);
193        }
194    }
195
196    public void writeSecKeyFields(Object o, EntityOutput output) {
197        try {
198            if (priKey != null && !priKey.isPrimitive) {
199                output.registerPriKeyObject(priKey.field.get(o));
200            }
201            if (superAccessor != null) {
202                superAccessor.writeSecKeyFields(o, output);
203            }
204            for (int i = 0; i < secKeys.length; i += 1) {
205                secKeys[i].write(o, output);
206            }
207        } catch (IllegalAccessException e) {
208            throw new IllegalStateException(e);
209        }
210    }
211
212    public void readSecKeyFields(Object o,
213                                 EntityInput input,
214                                 int startField,
215                                 int endField,
216                                 int superLevel) {
217        try {
218            if (priKey != null && !priKey.isPrimitive) {
219                input.registerPriKeyObject(priKey.field.get(o));
220            }
221            if (superLevel != 0 && superAccessor != null) {
222                superAccessor.readSecKeyFields
223                    (o, input, startField, endField, superLevel - 1);
224            } else {
225                if (superLevel > 0) {
226                    throw new IllegalStateException
227                        ("Superclass does not exist");
228                }
229            }
230            if (superLevel <= 0) {
231                for (int i = startField;
232                     i <= endField && i < secKeys.length;
233                     i += 1) {
234                    secKeys[i].read(o, input);
235                }
236            }
237        } catch (IllegalAccessException e) {
238            throw new IllegalStateException(e);
239        }
240    }
241
242    public void writeNonKeyFields(Object o, EntityOutput output) {
243        try {
244            if (superAccessor != null) {
245                superAccessor.writeNonKeyFields(o, output);
246            }
247            for (int i = 0; i < nonKeys.length; i += 1) {
248                nonKeys[i].write(o, output);
249            }
250        } catch (IllegalAccessException e) {
251            throw new IllegalStateException(e);
252        }
253    }
254
255    public void readNonKeyFields(Object o,
256                                 EntityInput input,
257                                 int startField,
258                                 int endField,
259                                 int superLevel) {
260        try {
261            if (superLevel != 0 && superAccessor != null) {
262                superAccessor.readNonKeyFields
263                    (o, input, startField, endField, superLevel - 1);
264            } else {
265                if (superLevel > 0) {
266                    throw new IllegalStateException
267                        ("Superclass does not exist");
268                }
269            }
270            if (superLevel <= 0) {
271                for (int i = startField;
272                     i <= endField && i < nonKeys.length;
273                     i += 1) {
274                    nonKeys[i].read(o, input);
275                }
276            }
277        } catch (IllegalAccessException e) {
278            throw new IllegalStateException(e);
279        }
280    }
281
282    public Object getField(Object o,
283                           int field,
284                           int superLevel,
285                           boolean isSecField) {
286        if (superLevel > 0) {
287            return superAccessor.getField
288                (o, field, superLevel - 1, isSecField);
289        }
290        try {
291            Field fld =
292		isSecField ? secKeys[field].field : nonKeys[field].field;
293            return fld.get(o);
294        } catch (IllegalAccessException e) {
295            throw new IllegalStateException(e);
296        }
297    }
298
299    public void setField(Object o,
300                         int field,
301                         int superLevel,
302                         boolean isSecField,
303                         Object value) {
304        if (superLevel > 0) {
305            superAccessor.setField
306                (o, field, superLevel - 1, isSecField, value);
307	    return;
308        }
309        try {
310            Field fld =
311		isSecField ? secKeys[field].field : nonKeys[field].field;
312            fld.set(o, value);
313        } catch (IllegalAccessException e) {
314            throw new IllegalStateException(e);
315        }
316    }
317
318    /**
319     * Abstract base class for field access classes.
320     */
321    private static abstract class FieldAccess {
322
323        Field field;
324        boolean isPrimitive;
325
326        FieldAccess(Field field) {
327            this.field = field;
328            isPrimitive = field.getType().isPrimitive();
329        }
330
331        /**
332         * Writes a field.
333         */
334        abstract void write(Object o, EntityOutput out)
335            throws IllegalAccessException;
336
337        /**
338         * Reads a field.
339         */
340        abstract void read(Object o, EntityInput in)
341            throws IllegalAccessException;
342
343        /**
344         * Returns whether a field is null (for reference types) or zero (for
345         * primitive integer types).  This implementation handles the reference
346         * types.
347         */
348        boolean isNullOrZero(Object o)
349            throws IllegalAccessException {
350
351            return field.get(o) == null;
352        }
353    }
354
355    /**
356     * Access for fields with object types.
357     */
358    private static class ObjectAccess extends FieldAccess {
359
360        ObjectAccess(Field field) {
361            super(field);
362        }
363
364        @Override
365        void write(Object o, EntityOutput out)
366            throws IllegalAccessException {
367
368            out.writeObject(field.get(o), null);
369        }
370
371        @Override
372        void read(Object o, EntityInput in)
373            throws IllegalAccessException {
374
375            field.set(o, in.readObject());
376        }
377    }
378
379    /**
380     * Access for primary key fields and composite key fields with object
381     * types.
382     */
383    private static class KeyObjectAccess extends FieldAccess {
384
385        private Format format;
386
387        KeyObjectAccess(Field field, Format format) {
388            super(field);
389            this.format = format;
390        }
391
392        @Override
393        void write(Object o, EntityOutput out)
394            throws IllegalAccessException {
395
396            out.writeKeyObject(field.get(o), format);
397        }
398
399        @Override
400        void read(Object o, EntityInput in)
401            throws IllegalAccessException {
402
403            field.set(o, in.readKeyObject(format));
404        }
405    }
406
407    /**
408     * Access for fields with primitive types.
409     */
410    private static class PrimitiveAccess extends FieldAccess {
411
412        private SimpleFormat format;
413
414        PrimitiveAccess(Field field, SimpleFormat format) {
415            super(field);
416            this.format = format;
417        }
418
419        @Override
420        void write(Object o, EntityOutput out)
421            throws IllegalAccessException {
422
423            format.writePrimitiveField(o, out, field);
424        }
425
426        @Override
427        void read(Object o, EntityInput in)
428            throws IllegalAccessException {
429
430            format.readPrimitiveField(o, in, field);
431        }
432
433        @Override
434        boolean isNullOrZero(Object o)
435            throws IllegalAccessException {
436
437            return field.getLong(o) == 0;
438        }
439    }
440}
441