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 import java.lang.ref.WeakReference;
21 import java.util.Arrays;
22 import java.util.Comparator;
23 
24 /**
25  * Describes a field for the purpose of serialization. Classes can define the
26  * collection of fields that are serialized, which may be different from the set
27  * of all declared fields.
28  *
29  * @see ObjectOutputStream#writeFields()
30  * @see ObjectInputStream#readFields()
31  */
32 public class ObjectStreamField implements Comparable<Object> {
33 
34     // Declared name of the field
35     private String name;
36 
37     // Declared type of the field
38     private Object type;
39 
40     // offset of this field in the object
41     int offset;
42 
43     // Cached version of intern'ed type String
44     private String typeString;
45 
46     private boolean unshared;
47 
48     private boolean isDeserialized;
49 
50     /**
51      * Constructs an ObjectStreamField with the specified name and type.
52      *
53      * @param name
54      *            the name of the field.
55      * @param cl
56      *            the type of the field.
57      * @throws NullPointerException
58      *             if {@code name} or {@code cl} is {@code null}.
59      */
ObjectStreamField(String name, Class<?> cl)60     public ObjectStreamField(String name, Class<?> cl) {
61         if (name == null) {
62             throw new NullPointerException("name == null");
63         } else if (cl == null) {
64             throw new NullPointerException("cl == null");
65         }
66         this.name = name;
67         this.type = new WeakReference<Class<?>>(cl);
68     }
69 
70     /**
71      * Constructs an ObjectStreamField with the specified name, type and the
72      * indication if it is unshared.
73      *
74      * @param name
75      *            the name of the field.
76      * @param cl
77      *            the type of the field.
78      * @param unshared
79      *            {@code true} if the field is written and read unshared;
80      *            {@code false} otherwise.
81      * @throws NullPointerException
82      *             if {@code name} or {@code cl} is {@code null}.
83      * @see ObjectOutputStream#writeUnshared(Object)
84      */
ObjectStreamField(String name, Class<?> cl, boolean unshared)85     public ObjectStreamField(String name, Class<?> cl, boolean unshared) {
86         if (name == null) {
87             throw new NullPointerException("name == null");
88         } else if (cl == null) {
89             throw new NullPointerException("cl == null");
90         }
91         this.name = name;
92         this.type = (cl.getClassLoader() == null) ? cl : new WeakReference<Class<?>>(cl);
93         this.unshared = unshared;
94     }
95 
96     /**
97      * Constructs an ObjectStreamField with the given name and the given type.
98      * The type may be null.
99      *
100      * @param signature
101      *            A String representing the type of the field
102      * @param name
103      *            a String, the name of the field, or null
104      */
ObjectStreamField(String signature, String name)105     ObjectStreamField(String signature, String name) {
106         if (name == null) {
107             throw new NullPointerException("name == null");
108         }
109         this.name = name;
110         this.typeString = signature.replace('.', '/').intern();
111         defaultResolve();
112         this.isDeserialized = true;
113     }
114 
115     /**
116      * Compares this field descriptor to the specified one. Checks first if one
117      * of the compared fields has a primitive type and the other one not. If so,
118      * the field with the primitive type is considered to be "smaller". If both
119      * fields are equal, their names are compared.
120      *
121      * @param o
122      *            the object to compare with.
123      * @return -1 if this field is "smaller" than field {@code o}, 0 if both
124      *         fields are equal; 1 if this field is "greater" than field {@code
125      *         o}.
126      */
compareTo(Object o)127     public int compareTo(Object o) {
128         ObjectStreamField f = (ObjectStreamField) o;
129         boolean thisPrimitive = this.isPrimitive();
130         boolean fPrimitive = f.isPrimitive();
131 
132         // If one is primitive and the other isn't, we have enough info to
133         // compare
134         if (thisPrimitive != fPrimitive) {
135             return thisPrimitive ? -1 : 1;
136         }
137 
138         // Either both primitives or both not primitives. Compare based on name.
139         return this.getName().compareTo(f.getName());
140     }
141 
142     /**
143      * Gets the name of this field.
144      *
145      * @return the field's name.
146      */
getName()147     public String getName() {
148         return name;
149     }
150 
151     /**
152      * Gets the offset of this field in the object.
153      *
154      * @return this field's offset.
155      */
getOffset()156     public int getOffset() {
157         return offset;
158     }
159 
160     /**
161      * Return the type of the field the receiver represents, this is an internal
162      * method
163      *
164      * @return A Class object representing the type of the field
165      */
166     // Changed from private to default visibility for usage in ObjectStreamClass
getTypeInternal()167     /* package */ Class<?> getTypeInternal() {
168         if (type instanceof WeakReference) {
169             return (Class<?>) ((WeakReference<?>) type).get();
170         }
171         return (Class<?>) type;
172     }
173 
174     /**
175      * Gets the type of this field.
176      *
177      * @return a {@code Class} object representing the type of the field.
178      */
getType()179     public Class<?> getType() {
180         Class<?> cl = getTypeInternal();
181         if (isDeserialized && !cl.isPrimitive()) {
182             return Object.class;
183         }
184         return cl;
185     }
186 
187     /**
188      * Gets a character code for the type of this field. The following codes are
189      * used:
190      *
191      * <pre>
192      * B     byte
193      * C     char
194      * D     double
195      * F     float
196      * I     int
197      * J     long
198      * L     class or interface
199      * S     short
200      * Z     boolean
201      * [     array
202      * </pre>
203      *
204      * @return the field's type code.
205      */
getTypeCode()206     public char getTypeCode() {
207         return typeCodeOf(getTypeInternal());
208     }
209 
typeCodeOf(Class<?> type)210     private char typeCodeOf(Class<?> type) {
211         if (type == int.class) {
212             return 'I';
213         } else if (type == byte.class) {
214             return 'B';
215         } else if (type == char.class) {
216             return 'C';
217         } else if (type == short.class) {
218             return 'S';
219         } else if (type == boolean.class) {
220             return 'Z';
221         } else if (type == long.class) {
222             return 'J';
223         } else if (type == float.class) {
224             return 'F';
225         } else if (type == double.class) {
226             return 'D';
227         } else if (type.isArray()) {
228             return '[';
229         } else {
230             return 'L';
231         }
232     }
233 
234     /**
235      * Gets the type signature used by the VM to represent the type of this
236      * field.
237      *
238      * @return the signature of this field's class or {@code null} if this
239      *         field's type is primitive.
240      */
getTypeString()241     public String getTypeString() {
242         if (isPrimitive()) {
243             return null;
244         }
245         if (typeString == null) {
246             Class<?> t = getTypeInternal();
247             String typeName = t.getName().replace('.', '/');
248             String str = (t.isArray()) ? typeName : ("L" + typeName + ';');
249             typeString = str.intern();
250         }
251         return typeString;
252     }
253 
254     /**
255      * Indicates whether this field's type is a primitive type.
256      *
257      * @return {@code true} if this field's type is primitive; {@code false} if
258      *         the type of this field is a regular class.
259      */
isPrimitive()260     public boolean isPrimitive() {
261         Class<?> t = getTypeInternal();
262         return t != null && t.isPrimitive();
263     }
264 
writeField(DataOutputStream out)265     boolean writeField(DataOutputStream out) throws IOException {
266         Class<?> t = getTypeInternal();
267         out.writeByte(typeCodeOf(t));
268         out.writeUTF(name);
269         return (t != null && t.isPrimitive());
270     }
271 
272     /**
273      * Sets this field's offset in the object.
274      *
275      * @param newValue
276      *            the field's new offset.
277      */
setOffset(int newValue)278     protected void setOffset(int newValue) {
279         this.offset = newValue;
280     }
281 
282     /**
283      * Returns a string containing a concise, human-readable description of this
284      * field descriptor.
285      *
286      * @return a printable representation of this descriptor.
287      */
288     @Override
toString()289     public String toString() {
290         return this.getClass().getName() + '(' + getName() + ':' + getTypeInternal() + ')';
291     }
292 
resolve(ClassLoader loader)293     void resolve(ClassLoader loader) {
294         if (typeString == null && isPrimitive()) {
295             // primitive type declared in a serializable class
296             typeString = String.valueOf(getTypeCode());
297         }
298 
299         if (typeString.length() == 1) {
300             if (defaultResolve()) {
301                 return;
302             }
303         }
304 
305         String className = typeString.replace('/', '.');
306         if (className.charAt(0) == 'L') {
307             // remove L and ;
308             className = className.substring(1, className.length() - 1);
309         }
310         try {
311             Class<?> cl = Class.forName(className, false, loader);
312             type = (cl.getClassLoader() == null) ? cl : new WeakReference<Class<?>>(cl);
313         } catch (ClassNotFoundException e) {
314             // Ignored
315         }
316     }
317 
318     /**
319      * Indicates whether this field is unshared.
320      *
321      * @return {@code true} if this field is unshared, {@code false} otherwise.
322      */
isUnshared()323     public boolean isUnshared() {
324         return unshared;
325     }
326 
setUnshared(boolean unshared)327     void setUnshared(boolean unshared) {
328         this.unshared = unshared;
329     }
330 
331     /**
332      * Resolves typeString into type. Returns true if the type is primitive
333      * and false otherwise.
334      */
defaultResolve()335     private boolean defaultResolve() {
336         switch (typeString.charAt(0)) {
337         case 'I':
338             type = int.class;
339             return true;
340         case 'B':
341             type = byte.class;
342             return true;
343         case 'C':
344             type = char.class;
345             return true;
346         case 'S':
347             type = short.class;
348             return true;
349         case 'Z':
350             type = boolean.class;
351             return true;
352         case 'J':
353             type = long.class;
354             return true;
355         case 'F':
356             type = float.class;
357             return true;
358         case 'D':
359             type = double.class;
360             return true;
361         default:
362             type = Object.class;
363             return false;
364         }
365     }
366 }
367