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,
80            ObjectStreamField[] declared) {
81        super();
82        // We assume the slots are already sorted in the right shape for dumping
83        buildSlots(fields);
84        declaredFields = declared;
85    }
86
87    /**
88     * Build emulated slots that correspond to emulated fields. A slot is a
89     * field descriptor (ObjectStreamField) plus the actual value it holds.
90     *
91     * @param fields
92     *            an array of ObjectStreamField, which describe the fields to be
93     *            emulated (names, types, etc).
94     */
95    private void buildSlots(ObjectStreamField[] fields) {
96        slotsToSerialize = new ObjectSlot[fields.length];
97        for (int i = 0; i < fields.length; i++) {
98            ObjectSlot s = new ObjectSlot();
99            slotsToSerialize[i] = s;
100            s.field = fields[i];
101        }
102        // We assume the slots are already sorted in the right shape for dumping
103    }
104
105    /**
106     * Returns {@code true} indicating the field called {@code name} has not had
107     * a value explicitly assigned and that it still holds a default value for
108     * its type, or {@code false} indicating that the field named has been
109     * assigned a value explicitly.
110     *
111     * @param name
112     *            the name of the field to test.
113     * @return {@code true} if {@code name} still holds its default value,
114     *         {@code false} otherwise
115     *
116     * @throws IllegalArgumentException
117     *             if {@code name} is {@code null}
118     */
119    public boolean defaulted(String name) throws IllegalArgumentException {
120        ObjectSlot slot = findSlot(name, null);
121        if (slot == null) {
122            throw new IllegalArgumentException("no field '" + name + "'");
123        }
124        return slot.defaulted;
125    }
126
127    /**
128     * Finds and returns an ObjectSlot that corresponds to a field named {@code
129     * fieldName} and type {@code fieldType}. If the field type {@code
130     * fieldType} corresponds to a primitive type, the field type has to match
131     * exactly or {@code null} is returned. If the field type {@code fieldType}
132     * corresponds to an object type, the field type has to be compatible in
133     * terms of assignment, or null is returned. If {@code fieldType} is {@code
134     * null}, no such compatibility checking is performed and the slot is
135     * returned.
136     *
137     * @param fieldName
138     *            the name of the field to find
139     * @param fieldType
140     *            the type of the field. This will be used to test
141     *            compatibility. If {@code null}, no testing is done, the
142     *            corresponding slot is returned.
143     * @return the object slot, or {@code null} if there is no field with that
144     *         name, or no compatible field (relative to {@code fieldType})
145     */
146    private ObjectSlot findSlot(String fieldName, Class<?> fieldType) {
147        boolean isPrimitive = fieldType != null && fieldType.isPrimitive();
148
149        for (int i = 0; i < slotsToSerialize.length; i++) {
150            ObjectSlot slot = slotsToSerialize[i];
151            if (slot.field.getName().equals(fieldName)) {
152                if (isPrimitive) {
153                    // Looking for a primitive type field. Types must match
154                    // *exactly*
155                    if (slot.field.getType() == fieldType) {
156                        return slot;
157                    }
158                } else {
159                    // Looking for a non-primitive type field.
160                    if (fieldType == null) {
161                        return slot; // Null means we take anything
162                    }
163                    // Types must be compatible (assignment)
164                    if (slot.field.getType().isAssignableFrom(fieldType)) {
165                        return slot;
166                    }
167                }
168            }
169        }
170
171        if (declaredFields != null) {
172            for (int i = 0; i < declaredFields.length; i++) {
173                ObjectStreamField field = declaredFields[i];
174                if (field.getName().equals(fieldName)) {
175                    if (isPrimitive ? field.getType() == fieldType
176                            : fieldType == null
177                                    || field.getType().isAssignableFrom(
178                                            fieldType)) {
179                        ObjectSlot slot = new ObjectSlot();
180                        slot.field = field;
181                        slot.defaulted = true;
182                        return slot;
183                    }
184                }
185            }
186        }
187        return null;
188    }
189
190    /**
191     * Finds and returns the byte value of a given field named {@code name}
192     * in the receiver. If the field has not been assigned any value yet, the
193     * default value {@code defaultValue} is returned instead.
194     *
195     * @param name
196     *            the name of the field to find.
197     * @param defaultValue
198     *            return value in case the field has not been assigned to yet.
199     * @return the value of the given field if it has been assigned, the default
200     *         value otherwise.
201     *
202     * @throws IllegalArgumentException
203     *             if the corresponding field can not be found.
204     */
205    public byte get(String name, byte defaultValue)
206            throws IllegalArgumentException {
207        ObjectSlot slot = findSlot(name, Byte.TYPE);
208        // if not initialized yet, we give the default value
209        if (slot == null) {
210            throw new IllegalArgumentException("no byte field '" + name + "'");
211        }
212        return slot.defaulted ? defaultValue : ((Byte) slot.fieldValue)
213                .byteValue();
214    }
215
216    /**
217     * Finds and returns the char value of a given field named {@code name} in the
218     * receiver. If the field has not been assigned any value yet, the default
219     * value {@code defaultValue} is returned instead.
220     *
221     * @param name
222     *            the name of the field to find.
223     * @param defaultValue
224     *            return value in case the field has not been assigned to yet.
225     * @return the value of the given field if it has been assigned, the default
226     *         value otherwise.
227     *
228     * @throws IllegalArgumentException
229     *             if the corresponding field can not be found.
230     */
231    public char get(String name, char defaultValue)
232            throws IllegalArgumentException {
233        ObjectSlot slot = findSlot(name, Character.TYPE);
234        // if not initialized yet, we give the default value
235        if (slot == null) {
236            throw new IllegalArgumentException("no char field '" + name + "'");
237        }
238        return slot.defaulted ? defaultValue : ((Character) slot.fieldValue)
239                .charValue();
240    }
241
242    /**
243     * Finds and returns the double value of a given field named {@code name}
244     * in the receiver. If the field has not been assigned any value yet, the
245     * default value {@code defaultValue} is returned instead.
246     *
247     * @param name
248     *            the name of the field to find.
249     * @param defaultValue
250     *            return value in case the field has not been assigned to yet.
251     * @return the value of the given field if it has been assigned, the default
252     *         value otherwise.
253     *
254     * @throws IllegalArgumentException
255     *             if the corresponding field can not be found.
256     */
257    public double get(String name, double defaultValue)
258            throws IllegalArgumentException {
259        ObjectSlot slot = findSlot(name, Double.TYPE);
260        // if not initialized yet, we give the default value
261        if (slot == null) {
262            throw new IllegalArgumentException("no double field '" + name + "'");
263        }
264        return slot.defaulted ? defaultValue : ((Double) slot.fieldValue)
265                .doubleValue();
266    }
267
268    /**
269     * Finds and returns the float value of a given field named {@code name} in
270     * the receiver. If the field has not been assigned any value yet, the
271     * default value {@code defaultValue} is returned instead.
272     *
273     * @param name
274     *            the name of the field to find.
275     * @param defaultValue
276     *            return value in case the field has not been assigned to yet.
277     * @return the value of the given field if it has been assigned, the default
278     *         value otherwise.
279     *
280     * @throws IllegalArgumentException
281     *             if the corresponding field can not be found.
282     */
283    public float get(String name, float defaultValue)
284            throws IllegalArgumentException {
285        ObjectSlot slot = findSlot(name, Float.TYPE);
286        // if not initialized yet, we give the default value
287        if (slot == null) {
288            throw new IllegalArgumentException("no float field '" + name + "'");
289        }
290        return slot.defaulted ? defaultValue : ((Float) slot.fieldValue)
291                .floatValue();
292    }
293
294    /**
295     * Finds and returns the int value of a given field named {@code name} in the
296     * receiver. If the field has not been assigned any value yet, the default
297     * value {@code defaultValue} is returned instead.
298     *
299     * @param name
300     *            the name of the field to find.
301     * @param defaultValue
302     *            return value in case the field has not been assigned to yet.
303     * @return the value of the given field if it has been assigned, the default
304     *         value otherwise.
305     *
306     * @throws IllegalArgumentException
307     *             if the corresponding field can not be found.
308     */
309    public int get(String name, int defaultValue)
310            throws IllegalArgumentException {
311        ObjectSlot slot = findSlot(name, Integer.TYPE);
312        // if not initialized yet, we give the default value
313        if (slot == null) {
314            throw new IllegalArgumentException("no int field '" + name + "'");
315        }
316        return slot.defaulted ? defaultValue : ((Integer) slot.fieldValue)
317                .intValue();
318    }
319
320    /**
321     * Finds and returns the long value of a given field named {@code name} in the
322     * receiver. If the field has not been assigned any value yet, the default
323     * value {@code defaultValue} is returned instead.
324     *
325     * @param name
326     *            the name of the field to find.
327     * @param defaultValue
328     *            return value in case the field has not been assigned to yet.
329     * @return the value of the given field if it has been assigned, the default
330     *         value otherwise.
331     *
332     * @throws IllegalArgumentException
333     *             if the corresponding field can not be found.
334     */
335    public long get(String name, long defaultValue)
336            throws IllegalArgumentException {
337        ObjectSlot slot = findSlot(name, Long.TYPE);
338        // if not initialized yet, we give the default value
339        if (slot == null) {
340            throw new IllegalArgumentException("no long field '" + name + "'");
341        }
342        return slot.defaulted ? defaultValue : ((Long) slot.fieldValue)
343                .longValue();
344    }
345
346    /**
347     * Finds and returns the Object value of a given field named {@code name} in
348     * the receiver. If the field has not been assigned any value yet, the
349     * default value {@code defaultValue} is returned instead.
350     *
351     * @param name
352     *            the name of the field to find.
353     * @param defaultValue
354     *            return value in case the field has not been assigned to yet.
355     * @return the value of the given field if it has been assigned, the default
356     *         value otherwise.
357     *
358     * @throws IllegalArgumentException
359     *             if the corresponding field can not be found.
360     */
361    public Object get(String name, Object defaultValue)
362            throws IllegalArgumentException {
363        ObjectSlot slot = findSlot(name, null);
364        // if not initialized yet, we give the default value
365        if (slot == null || slot.field.getType().isPrimitive()) {
366            throw new IllegalArgumentException("no Object field '" + name + "'");
367        }
368        return slot.defaulted ? defaultValue : slot.fieldValue;
369    }
370
371    /**
372     * Finds and returns the short value of a given field named {@code name} in
373     * the receiver. If the field has not been assigned any value yet, the
374     * default value {@code defaultValue} is returned instead.
375     *
376     * @param name
377     *            the name of the field to find.
378     * @param defaultValue
379     *            return value in case the field has not been assigned to yet.
380     * @return the value of the given field if it has been assigned, the default
381     *         value otherwise.
382     *
383     * @throws IllegalArgumentException
384     *             if the corresponding field can not be found.
385     */
386    public short get(String name, short defaultValue)
387            throws IllegalArgumentException {
388        ObjectSlot slot = findSlot(name, Short.TYPE);
389        // if not initialized yet, we give the default value
390        if (slot == null) {
391            throw new IllegalArgumentException("no short field '" + name + "'");
392        }
393        return slot.defaulted ? defaultValue : ((Short) slot.fieldValue)
394                .shortValue();
395    }
396
397    /**
398     * Finds and returns the boolean value of a given field named {@code name} in
399     * the receiver. If the field has not been assigned any value yet, the
400     * default value {@code defaultValue} is returned instead.
401     *
402     * @param name
403     *            the name of the field to find.
404     * @param defaultValue
405     *            return value in case the field has not been assigned to yet.
406     * @return the value of the given field if it has been assigned, the default
407     *         value otherwise.
408     *
409     * @throws IllegalArgumentException
410     *             if the corresponding field can not be found.
411     */
412    public boolean get(String name, boolean defaultValue)
413            throws IllegalArgumentException {
414        ObjectSlot slot = findSlot(name, Boolean.TYPE);
415        // if not initialized yet, we give the default value
416        if (slot == null) {
417            throw new IllegalArgumentException("no boolean field '" + name + "'");
418        }
419        return slot.defaulted ? defaultValue : ((Boolean) slot.fieldValue)
420                .booleanValue();
421    }
422
423    /**
424     * Find and set the byte value of a given field named {@code name} in the
425     * receiver.
426     *
427     * @param name
428     *            the name of the field to set.
429     * @param value
430     *            new value for the field.
431     *
432     * @throws IllegalArgumentException
433     *             if the corresponding field can not be found.
434     */
435    public void put(String name, byte value) throws IllegalArgumentException {
436        ObjectSlot slot = findSlot(name, Byte.TYPE);
437        if (slot == null) {
438            throw new IllegalArgumentException("no byte field '" + name + "'");
439        }
440        slot.fieldValue = Byte.valueOf(value);
441        slot.defaulted = false; // No longer default value
442    }
443
444    /**
445     * Find and set the char value of a given field named {@code name} in the
446     * receiver.
447     *
448     * @param name
449     *            the name of the field to set.
450     * @param value
451     *            new value for the field.
452     *
453     * @throws IllegalArgumentException
454     *             if the corresponding field can not be found.
455     */
456    public void put(String name, char value) throws IllegalArgumentException {
457        ObjectSlot slot = findSlot(name, Character.TYPE);
458        if (slot == null) {
459            throw new IllegalArgumentException("no char field '" + name + "'");
460        }
461        slot.fieldValue = Character.valueOf(value);
462        slot.defaulted = false; // No longer default value
463    }
464
465    /**
466     * Find and set the double value of a given field named {@code name} in the
467     * receiver.
468     *
469     * @param name
470     *            the name of the field to set.
471     * @param value
472     *            new value for the field.
473     *
474     * @throws IllegalArgumentException
475     *             if the corresponding field can not be found.
476     */
477    public void put(String name, double value) throws IllegalArgumentException {
478        ObjectSlot slot = findSlot(name, Double.TYPE);
479        if (slot == null) {
480            throw new IllegalArgumentException("no double field '" + name + "'");
481        }
482        slot.fieldValue = Double.valueOf(value);
483        slot.defaulted = false; // No longer default value
484    }
485
486    /**
487     * Find and set the float value of a given field named {@code name} in the
488     * receiver.
489     *
490     * @param name
491     *            the name of the field to set.
492     * @param value
493     *            new value for the field.
494     *
495     * @throws IllegalArgumentException
496     *             if the corresponding field can not be found.
497     */
498    public void put(String name, float value) throws IllegalArgumentException {
499        ObjectSlot slot = findSlot(name, Float.TYPE);
500        if (slot == null) {
501            throw new IllegalArgumentException("no float field '" + name + "'");
502        }
503        slot.fieldValue = Float.valueOf(value);
504        slot.defaulted = false; // No longer default value
505    }
506
507    /**
508     * Find and set the int value of a given field named {@code name} in the
509     * receiver.
510     *
511     * @param name
512     *            the name of the field to set.
513     * @param value
514     *            new value for the field.
515     *
516     * @throws IllegalArgumentException
517     *             if the corresponding field can not be found.
518     */
519    public void put(String name, int value) throws IllegalArgumentException {
520        ObjectSlot slot = findSlot(name, Integer.TYPE);
521        if (slot == null) {
522            throw new IllegalArgumentException("no integer field '" + name + "'");
523        }
524        slot.fieldValue = Integer.valueOf(value);
525        slot.defaulted = false; // No longer default value
526    }
527
528    /**
529     * Find and set the long value of a given field named {@code name} in the
530     * receiver.
531     *
532     * @param name
533     *            the name of the field to set.
534     * @param value
535     *            new value for the field.
536     *
537     * @throws IllegalArgumentException
538     *             if the corresponding field can not be found.
539     */
540    public void put(String name, long value) throws IllegalArgumentException {
541        ObjectSlot slot = findSlot(name, Long.TYPE);
542        if (slot == null) {
543            throw new IllegalArgumentException("no long field '" + name + "'");
544        }
545        slot.fieldValue = Long.valueOf(value);
546        slot.defaulted = false; // No longer default value
547    }
548
549    /**
550     * Find and set the Object value of a given field named {@code name} in the
551     * receiver.
552     *
553     * @param name
554     *            the name of the field to set.
555     * @param value
556     *            new value for the field.
557     *
558     * @throws IllegalArgumentException
559     *             if the corresponding field can not be found.
560     */
561    public void put(String name, Object value) throws IllegalArgumentException {
562        Class<?> valueClass = null;
563        if (value != null) {
564            valueClass = value.getClass();
565        }
566        ObjectSlot slot = findSlot(name, valueClass);
567        if (slot == null) {
568            throw new IllegalArgumentException("no Object field '" + name + "'");
569        }
570        slot.fieldValue = value;
571        slot.defaulted = false; // No longer default value
572    }
573
574    /**
575     * Find and set the short value of a given field named {@code name} in the
576     * receiver.
577     *
578     * @param name
579     *            the name of the field to set.
580     * @param value
581     *            new value for the field.
582     *
583     * @throws IllegalArgumentException
584     *             if the corresponding field can not be found.
585     */
586    public void put(String name, short value) throws IllegalArgumentException {
587        ObjectSlot slot = findSlot(name, Short.TYPE);
588        if (slot == null) {
589            throw new IllegalArgumentException("no short field '" + name + "'");
590        }
591        slot.fieldValue = Short.valueOf(value);
592        slot.defaulted = false; // No longer default value
593    }
594
595    /**
596     * Find and set the boolean value of a given field named {@code name} in the
597     * receiver.
598     *
599     * @param name
600     *            the name of the field to set.
601     * @param value
602     *            new value for the field.
603     *
604     * @throws IllegalArgumentException
605     *             if the corresponding field can not be found.
606     */
607    public void put(String name, boolean value) throws IllegalArgumentException {
608        ObjectSlot slot = findSlot(name, Boolean.TYPE);
609        if (slot == null) {
610            throw new IllegalArgumentException("no boolean field '" + name + "'");
611        }
612        slot.fieldValue = Boolean.valueOf(value);
613        slot.defaulted = false; // No longer default value
614    }
615
616    /**
617     * Return the array of ObjectSlot the receiver represents.
618     *
619     * @return array of ObjectSlot the receiver represents.
620     */
621    public ObjectSlot[] slots() {
622        return slotsToSerialize;
623    }
624}
625