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