1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.io;
19
20/**
21 * An EmulatedFields is an object that represents a set of emulated fields for
22 * an object being dumped or loaded. It allows objects to be dumped with a shape
23 * different than the fields they were declared to have.
24 *
25 * @see ObjectInputStream.GetField
26 * @see ObjectOutputStream.PutField
27 * @see EmulatedFieldsForLoading
28 * @see EmulatedFieldsForDumping
29 */
30class EmulatedFields {
31
32    // A slot is a field plus its value
33    static class ObjectSlot {
34
35        // Field descriptor
36        ObjectStreamField field;
37
38        // Actual value this emulated field holds
39        Object fieldValue;
40
41        // If this field has a default value (true) or something has been
42        // assigned (false)
43        boolean defaulted = true;
44
45        /**
46         * Returns the descriptor for this emulated field.
47         *
48         * @return the field descriptor
49         */
50        public ObjectStreamField getField() {
51            return field;
52        }
53
54        /**
55         * Returns the value held by this emulated field.
56         *
57         * @return the field value
58         */
59        public Object getFieldValue() {
60            return fieldValue;
61        }
62    }
63
64    // The collection of slots the receiver represents
65    private ObjectSlot[] slotsToSerialize;
66
67    private ObjectStreamField[] declaredFields;
68
69    /**
70     * Constructs a new instance of EmulatedFields.
71     *
72     * @param fields
73     *            an array of ObjectStreamFields, which describe the fields to
74     *            be emulated (names, types, etc).
75     * @param declared
76     *            an array of ObjectStreamFields, which describe the declared
77     *            fields.
78     */
79    public EmulatedFields(ObjectStreamField[] fields, ObjectStreamField[] declared) {
80        // We assume the slots are already sorted in the right shape for dumping
81        buildSlots(fields);
82        declaredFields = declared;
83    }
84
85    /**
86     * Build emulated slots that correspond to emulated fields. A slot is a
87     * field descriptor (ObjectStreamField) plus the actual value it holds.
88     *
89     * @param fields
90     *            an array of ObjectStreamField, which describe the fields to be
91     *            emulated (names, types, etc).
92     */
93    private void buildSlots(ObjectStreamField[] fields) {
94        slotsToSerialize = new ObjectSlot[fields.length];
95        for (int i = 0; i < fields.length; i++) {
96            ObjectSlot s = new ObjectSlot();
97            slotsToSerialize[i] = s;
98            s.field = fields[i];
99        }
100        // We assume the slots are already sorted in the right shape for dumping
101    }
102
103    /**
104     * Returns {@code true} indicating the field called {@code name} has not had
105     * a value explicitly assigned and that it still holds a default value for
106     * its type, or {@code false} indicating that the field named has been
107     * assigned a value explicitly.
108     *
109     * @param name
110     *            the name of the field to test.
111     * @return {@code true} if {@code name} still holds its default value,
112     *         {@code false} otherwise
113     *
114     * @throws IllegalArgumentException
115     *             if {@code name} is {@code null}
116     */
117    public boolean defaulted(String name) throws IllegalArgumentException {
118        ObjectSlot slot = findSlot(name, null);
119        if (slot == null) {
120            throw new IllegalArgumentException("no field '" + name + "'");
121        }
122        return slot.defaulted;
123    }
124
125    /**
126     * Finds and returns an ObjectSlot that corresponds to a field named {@code
127     * fieldName} and type {@code fieldType}. If the field type {@code
128     * fieldType} corresponds to a primitive type, the field type has to match
129     * exactly or {@code null} is returned. If the field type {@code fieldType}
130     * corresponds to an object type, the field type has to be compatible in
131     * terms of assignment, or null is returned. If {@code fieldType} is {@code
132     * null}, no such compatibility checking is performed and the slot is
133     * returned.
134     *
135     * @param fieldName
136     *            the name of the field to find
137     * @param fieldType
138     *            the type of the field. This will be used to test
139     *            compatibility. If {@code null}, no testing is done, the
140     *            corresponding slot is returned.
141     * @return the object slot, or {@code null} if there is no field with that
142     *         name, or no compatible field (relative to {@code fieldType})
143     */
144    private ObjectSlot findSlot(String fieldName, Class<?> fieldType) {
145        boolean isPrimitive = fieldType != null && fieldType.isPrimitive();
146        for (int i = 0; i < slotsToSerialize.length; i++) {
147            ObjectSlot slot = slotsToSerialize[i];
148            if (slot.field.getName().equals(fieldName)) {
149                if (isPrimitive) {
150                    // Looking for a primitive type field. Types must match
151                    // *exactly*
152                    if (slot.field.getType() == fieldType) {
153                        return slot;
154                    }
155                } else {
156                    // Looking for a non-primitive type field.
157                    if (fieldType == null) {
158                        return slot; // Null means we take anything
159                    }
160                    // Types must be compatible (assignment)
161                    if (slot.field.getType().isAssignableFrom(fieldType)) {
162                        return slot;
163                    }
164                }
165            }
166        }
167
168        if (declaredFields != null) {
169            for (int i = 0; i < declaredFields.length; i++) {
170                ObjectStreamField field = declaredFields[i];
171                if (field.getName().equals(fieldName)) {
172                    if (isPrimitive ? fieldType == field.getType() : fieldType == null ||
173                            field.getType().isAssignableFrom(fieldType)) {
174                        ObjectSlot slot = new ObjectSlot();
175                        slot.field = field;
176                        slot.defaulted = true;
177                        return slot;
178                    }
179                }
180            }
181        }
182        return null;
183    }
184
185    private ObjectSlot findMandatorySlot(String name, Class<?> type) {
186        ObjectSlot slot = findSlot(name, type);
187        if (slot == null || (type == null && slot.field.getType().isPrimitive())) {
188            throw new IllegalArgumentException("no field '" + name + "' of type " + type);
189        }
190        return slot;
191    }
192
193    /**
194     * Finds and returns the byte value of a given field named {@code name}
195     * in the receiver. If the field has not been assigned any value yet, the
196     * default value {@code defaultValue} is returned instead.
197     *
198     * @param name
199     *            the name of the field to find.
200     * @param defaultValue
201     *            return value in case the field has not been assigned to yet.
202     * @return the value of the given field if it has been assigned, the default
203     *         value otherwise.
204     *
205     * @throws IllegalArgumentException
206     *             if the corresponding field can not be found.
207     */
208    public byte get(String name, byte defaultValue) throws IllegalArgumentException {
209        ObjectSlot slot = findMandatorySlot(name, byte.class);
210        return slot.defaulted ? defaultValue : ((Byte) slot.fieldValue).byteValue();
211    }
212
213    /**
214     * Finds and returns the char value of a given field named {@code name} in the
215     * receiver. If the field has not been assigned any value yet, the default
216     * value {@code defaultValue} is returned instead.
217     *
218     * @param name
219     *            the name of the field to find.
220     * @param defaultValue
221     *            return value in case the field has not been assigned to yet.
222     * @return the value of the given field if it has been assigned, the default
223     *         value otherwise.
224     *
225     * @throws IllegalArgumentException
226     *             if the corresponding field can not be found.
227     */
228    public char get(String name, char defaultValue) throws IllegalArgumentException {
229        ObjectSlot slot = findMandatorySlot(name, char.class);
230        return slot.defaulted ? defaultValue : ((Character) slot.fieldValue).charValue();
231    }
232
233    /**
234     * Finds and returns the double value of a given field named {@code name}
235     * in the receiver. If the field has not been assigned any value yet, the
236     * default value {@code defaultValue} is returned instead.
237     *
238     * @param name
239     *            the name of the field to find.
240     * @param defaultValue
241     *            return value in case the field has not been assigned to yet.
242     * @return the value of the given field if it has been assigned, the default
243     *         value otherwise.
244     *
245     * @throws IllegalArgumentException
246     *             if the corresponding field can not be found.
247     */
248    public double get(String name, double defaultValue) throws IllegalArgumentException {
249        ObjectSlot slot = findMandatorySlot(name, double.class);
250        return slot.defaulted ? defaultValue : ((Double) slot.fieldValue).doubleValue();
251    }
252
253    /**
254     * Finds and returns the float value of a given field named {@code name} in
255     * the receiver. If the field has not been assigned any value yet, the
256     * default value {@code defaultValue} is returned instead.
257     *
258     * @param name
259     *            the name of the field to find.
260     * @param defaultValue
261     *            return value in case the field has not been assigned to yet.
262     * @return the value of the given field if it has been assigned, the default
263     *         value otherwise.
264     *
265     * @throws IllegalArgumentException
266     *             if the corresponding field can not be found.
267     */
268    public float get(String name, float defaultValue) throws IllegalArgumentException {
269        ObjectSlot slot = findMandatorySlot(name, float.class);
270        return slot.defaulted ? defaultValue : ((Float) slot.fieldValue).floatValue();
271    }
272
273    /**
274     * Finds and returns the int value of a given field named {@code name} in the
275     * receiver. If the field has not been assigned any value yet, the default
276     * value {@code defaultValue} is returned instead.
277     *
278     * @param name
279     *            the name of the field to find.
280     * @param defaultValue
281     *            return value in case the field has not been assigned to yet.
282     * @return the value of the given field if it has been assigned, the default
283     *         value otherwise.
284     *
285     * @throws IllegalArgumentException
286     *             if the corresponding field can not be found.
287     */
288    public int get(String name, int defaultValue) throws IllegalArgumentException {
289        ObjectSlot slot = findMandatorySlot(name, int.class);
290        return slot.defaulted ? defaultValue : ((Integer) slot.fieldValue).intValue();
291    }
292
293    /**
294     * Finds and returns the long value of a given field named {@code name} in the
295     * receiver. If the field has not been assigned any value yet, the default
296     * value {@code defaultValue} is returned instead.
297     *
298     * @param name
299     *            the name of the field to find.
300     * @param defaultValue
301     *            return value in case the field has not been assigned to yet.
302     * @return the value of the given field if it has been assigned, the default
303     *         value otherwise.
304     *
305     * @throws IllegalArgumentException
306     *             if the corresponding field can not be found.
307     */
308    public long get(String name, long defaultValue) throws IllegalArgumentException {
309        ObjectSlot slot = findMandatorySlot(name, long.class);
310        return slot.defaulted ? defaultValue : ((Long) slot.fieldValue).longValue();
311    }
312
313    /**
314     * Finds and returns the Object value of a given field named {@code name} in
315     * the receiver. If the field has not been assigned any value yet, the
316     * default value {@code defaultValue} is returned instead.
317     *
318     * @param name
319     *            the name of the field to find.
320     * @param defaultValue
321     *            return value in case the field has not been assigned to yet.
322     * @return the value of the given field if it has been assigned, the default
323     *         value otherwise.
324     *
325     * @throws IllegalArgumentException
326     *             if the corresponding field can not be found.
327     */
328    public Object get(String name, Object defaultValue) throws IllegalArgumentException {
329        ObjectSlot slot = findMandatorySlot(name, null);
330        return slot.defaulted ? defaultValue : slot.fieldValue;
331    }
332
333    /**
334     * Finds and returns the short value of a given field named {@code name} in
335     * the receiver. If the field has not been assigned any value yet, the
336     * default value {@code defaultValue} is returned instead.
337     *
338     * @param name
339     *            the name of the field to find.
340     * @param defaultValue
341     *            return value in case the field has not been assigned to yet.
342     * @return the value of the given field if it has been assigned, the default
343     *         value otherwise.
344     *
345     * @throws IllegalArgumentException
346     *             if the corresponding field can not be found.
347     */
348    public short get(String name, short defaultValue) throws IllegalArgumentException {
349        ObjectSlot slot = findMandatorySlot(name, short.class);
350        return slot.defaulted ? defaultValue : ((Short) slot.fieldValue).shortValue();
351    }
352
353    /**
354     * Finds and returns the boolean value of a given field named {@code name} in
355     * the receiver. If the field has not been assigned any value yet, the
356     * default value {@code defaultValue} is returned instead.
357     *
358     * @param name
359     *            the name of the field to find.
360     * @param defaultValue
361     *            return value in case the field has not been assigned to yet.
362     * @return the value of the given field if it has been assigned, the default
363     *         value otherwise.
364     *
365     * @throws IllegalArgumentException
366     *             if the corresponding field can not be found.
367     */
368    public boolean get(String name, boolean defaultValue) throws IllegalArgumentException {
369        ObjectSlot slot = findMandatorySlot(name, boolean.class);
370        return slot.defaulted ? defaultValue : ((Boolean) slot.fieldValue).booleanValue();
371    }
372
373    /**
374     * Find and set the byte value of a given field named {@code name} in the
375     * receiver.
376     *
377     * @param name
378     *            the name of the field to set.
379     * @param value
380     *            new value for the field.
381     *
382     * @throws IllegalArgumentException
383     *             if the corresponding field can not be found.
384     */
385    public void put(String name, byte value) throws IllegalArgumentException {
386        ObjectSlot slot = findMandatorySlot(name, byte.class);
387        slot.fieldValue = Byte.valueOf(value);
388        slot.defaulted = false; // No longer default value
389    }
390
391    /**
392     * Find and set the char value of a given field named {@code name} in the
393     * receiver.
394     *
395     * @param name
396     *            the name of the field to set.
397     * @param value
398     *            new value for the field.
399     *
400     * @throws IllegalArgumentException
401     *             if the corresponding field can not be found.
402     */
403    public void put(String name, char value) throws IllegalArgumentException {
404        ObjectSlot slot = findMandatorySlot(name, char.class);
405        slot.fieldValue = Character.valueOf(value);
406        slot.defaulted = false; // No longer default value
407    }
408
409    /**
410     * Find and set the double value of a given field named {@code name} in the
411     * receiver.
412     *
413     * @param name
414     *            the name of the field to set.
415     * @param value
416     *            new value for the field.
417     *
418     * @throws IllegalArgumentException
419     *             if the corresponding field can not be found.
420     */
421    public void put(String name, double value) throws IllegalArgumentException {
422        ObjectSlot slot = findMandatorySlot(name, double.class);
423        slot.fieldValue = Double.valueOf(value);
424        slot.defaulted = false; // No longer default value
425    }
426
427    /**
428     * Find and set the float value of a given field named {@code name} in the
429     * receiver.
430     *
431     * @param name
432     *            the name of the field to set.
433     * @param value
434     *            new value for the field.
435     *
436     * @throws IllegalArgumentException
437     *             if the corresponding field can not be found.
438     */
439    public void put(String name, float value) throws IllegalArgumentException {
440        ObjectSlot slot = findMandatorySlot(name, float.class);
441        slot.fieldValue = Float.valueOf(value);
442        slot.defaulted = false; // No longer default value
443    }
444
445    /**
446     * Find and set the int value of a given field named {@code name} in the
447     * receiver.
448     *
449     * @param name
450     *            the name of the field to set.
451     * @param value
452     *            new value for the field.
453     *
454     * @throws IllegalArgumentException
455     *             if the corresponding field can not be found.
456     */
457    public void put(String name, int value) throws IllegalArgumentException {
458        ObjectSlot slot = findMandatorySlot(name, int.class);
459        slot.fieldValue = Integer.valueOf(value);
460        slot.defaulted = false; // No longer default value
461    }
462
463    /**
464     * Find and set the long value of a given field named {@code name} in the
465     * receiver.
466     *
467     * @param name
468     *            the name of the field to set.
469     * @param value
470     *            new value for the field.
471     *
472     * @throws IllegalArgumentException
473     *             if the corresponding field can not be found.
474     */
475    public void put(String name, long value) throws IllegalArgumentException {
476        ObjectSlot slot = findMandatorySlot(name, long.class);
477        slot.fieldValue = Long.valueOf(value);
478        slot.defaulted = false; // No longer default value
479    }
480
481    /**
482     * Find and set the Object value of a given field named {@code name} in the
483     * receiver.
484     *
485     * @param name
486     *            the name of the field to set.
487     * @param value
488     *            new value for the field.
489     *
490     * @throws IllegalArgumentException
491     *             if the corresponding field can not be found.
492     */
493    public void put(String name, Object value) throws IllegalArgumentException {
494        Class<?> valueClass = null;
495        if (value != null) {
496            valueClass = value.getClass();
497        }
498        ObjectSlot slot = findMandatorySlot(name, valueClass);
499        slot.fieldValue = value;
500        slot.defaulted = false; // No longer default value
501    }
502
503    /**
504     * Find and set the short value of a given field named {@code name} in the
505     * receiver.
506     *
507     * @param name
508     *            the name of the field to set.
509     * @param value
510     *            new value for the field.
511     *
512     * @throws IllegalArgumentException
513     *             if the corresponding field can not be found.
514     */
515    public void put(String name, short value) throws IllegalArgumentException {
516        ObjectSlot slot = findMandatorySlot(name, short.class);
517        slot.fieldValue = Short.valueOf(value);
518        slot.defaulted = false; // No longer default value
519    }
520
521    /**
522     * Find and set the boolean value of a given field named {@code name} in the
523     * receiver.
524     *
525     * @param name
526     *            the name of the field to set.
527     * @param value
528     *            new value for the field.
529     *
530     * @throws IllegalArgumentException
531     *             if the corresponding field can not be found.
532     */
533    public void put(String name, boolean value) throws IllegalArgumentException {
534        ObjectSlot slot = findMandatorySlot(name, boolean.class);
535        slot.fieldValue = Boolean.valueOf(value);
536        slot.defaulted = false; // No longer default value
537    }
538
539    /**
540     * Return the array of ObjectSlot the receiver represents.
541     *
542     * @return array of ObjectSlot the receiver represents.
543     */
544    public ObjectSlot[] slots() {
545        return slotsToSerialize;
546    }
547}
548