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 
18 package 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  */
30 class 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          */
getField()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          */
getFieldValue()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      */
EmulatedFields(ObjectStreamField[] fields, ObjectStreamField[] declared)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      */
buildSlots(ObjectStreamField[] fields)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      */
defaulted(String name)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      */
findSlot(String fieldName, Class<?> fieldType)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 
findMandatorySlot(String name, Class<?> type)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      */
get(String name, byte defaultValue)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      */
get(String name, char defaultValue)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      */
get(String name, double defaultValue)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      */
get(String name, float defaultValue)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      */
get(String name, int defaultValue)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      */
get(String name, long defaultValue)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      */
get(String name, Object defaultValue)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      */
get(String name, short defaultValue)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      */
get(String name, boolean defaultValue)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      */
put(String name, byte value)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      */
put(String name, char value)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      */
put(String name, double value)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      */
put(String name, float value)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      */
put(String name, int value)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      */
put(String name, long value)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      */
put(String name, Object value)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      */
put(String name, short value)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      */
put(String name, boolean value)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      */
slots()544     public ObjectSlot[] slots() {
545         return slotsToSerialize;
546     }
547 }
548