| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.io; |
| |
| import dalvik.system.VMStack; |
| import java.io.EmulatedFields.ObjectSlot; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import libcore.util.EmptyArray; |
| |
| /** |
| * A specialized {@link InputStream} that is able to read (deserialize) Java |
| * objects as well as primitive data types (int, byte, char etc.). The data has |
| * typically been saved using an ObjectOutputStream. |
| * |
| * @see ObjectOutputStream |
| * @see ObjectInput |
| * @see Serializable |
| * @see Externalizable |
| */ |
| public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants { |
| |
| // TODO: this is non-static to avoid sync contention. Would static be faster? |
| private InputStream emptyStream = new ByteArrayInputStream(EmptyArray.BYTE); |
| |
| // To put into objectsRead when reading unsharedObject |
| private static final Object UNSHARED_OBJ = new Object(); // $NON-LOCK-1$ |
| |
| // If the receiver has already read & not consumed a TC code |
| private boolean hasPushbackTC; |
| |
| // Push back TC code if the variable above is true |
| private byte pushbackTC; |
| |
| // How many nested levels to readObject. When we reach 0 we have to validate |
| // the graph then reset it |
| private int nestedLevels; |
| |
| // All objects are assigned an ID (integer handle) |
| private int nextHandle; |
| |
| // Where we read from |
| private DataInputStream input; |
| |
| // Where we read primitive types from |
| private DataInputStream primitiveTypes; |
| |
| // Where we keep primitive type data |
| private InputStream primitiveData = emptyStream; |
| |
| // Resolve object is a mechanism for replacement |
| private boolean enableResolve; |
| |
| /** |
| * All the objects we've read, indexed by their serialization handle (minus the base offset). |
| */ |
| private ArrayList<Object> objectsRead; |
| |
| // Used by defaultReadObject |
| private Object currentObject; |
| |
| // Used by defaultReadObject |
| private ObjectStreamClass currentClass; |
| |
| // All validations to be executed when the complete graph is read. See inner |
| // type below. |
| private InputValidationDesc[] validations; |
| |
| // Allows the receiver to decide if it needs to call readObjectOverride |
| private boolean subclassOverridingImplementation; |
| |
| // Original caller's class loader, used to perform class lookups |
| private ClassLoader callerClassLoader; |
| |
| // false when reading missing fields |
| private boolean mustResolve = true; |
| |
| // Handle for the current class descriptor |
| private int descriptorHandle = -1; |
| |
| private static final HashMap<String, Class<?>> PRIMITIVE_CLASSES = new HashMap<String, Class<?>>(); |
| static { |
| PRIMITIVE_CLASSES.put("boolean", boolean.class); |
| PRIMITIVE_CLASSES.put("byte", byte.class); |
| PRIMITIVE_CLASSES.put("char", char.class); |
| PRIMITIVE_CLASSES.put("double", double.class); |
| PRIMITIVE_CLASSES.put("float", float.class); |
| PRIMITIVE_CLASSES.put("int", int.class); |
| PRIMITIVE_CLASSES.put("long", long.class); |
| PRIMITIVE_CLASSES.put("short", short.class); |
| PRIMITIVE_CLASSES.put("void", void.class); |
| } |
| |
| // Internal type used to keep track of validators & corresponding priority |
| static class InputValidationDesc { |
| ObjectInputValidation validator; |
| |
| int priority; |
| } |
| |
| /** |
| * GetField is an inner class that provides access to the persistent fields |
| * read from the source stream. |
| */ |
| public abstract static class GetField { |
| /** |
| * Gets the ObjectStreamClass that describes a field. |
| * |
| * @return the descriptor class for a serialized field. |
| */ |
| public abstract ObjectStreamClass getObjectStreamClass(); |
| |
| /** |
| * Indicates if the field identified by {@code name} is defaulted. This |
| * means that it has no value in this stream. |
| * |
| * @param name |
| * the name of the field to check. |
| * @return {@code true} if the field is defaulted, {@code false} |
| * otherwise. |
| * @throws IllegalArgumentException |
| * if {@code name} does not identify a serializable field. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| */ |
| public abstract boolean defaulted(String name) throws IOException, |
| IllegalArgumentException; |
| |
| /** |
| * Gets the value of the boolean field identified by {@code name} from |
| * the persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code boolean}. |
| */ |
| public abstract boolean get(String name, boolean defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the character field identified by {@code name} from |
| * the persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code char}. |
| */ |
| public abstract char get(String name, char defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the byte field identified by {@code name} from the |
| * persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code byte}. |
| */ |
| public abstract byte get(String name, byte defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the short field identified by {@code name} from the |
| * persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code short}. |
| */ |
| public abstract short get(String name, short defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the integer field identified by {@code name} from |
| * the persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code int}. |
| */ |
| public abstract int get(String name, int defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the long field identified by {@code name} from the |
| * persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code long}. |
| */ |
| public abstract long get(String name, long defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the float field identified by {@code name} from the |
| * persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code float} is |
| * not {@code char}. |
| */ |
| public abstract float get(String name, float defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the double field identified by {@code name} from |
| * the persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code double}. |
| */ |
| public abstract double get(String name, double defaultValue) |
| throws IOException, IllegalArgumentException; |
| |
| /** |
| * Gets the value of the object field identified by {@code name} from |
| * the persistent field. |
| * |
| * @param name |
| * the name of the field to get. |
| * @param defaultValue |
| * the default value that is used if the field does not have |
| * a value when read from the source stream. |
| * @return the value of the field identified by {@code name}. |
| * @throws IOException |
| * if an error occurs while reading from the source input |
| * stream. |
| * @throws IllegalArgumentException |
| * if the type of the field identified by {@code name} is |
| * not {@code Object}. |
| */ |
| public abstract Object get(String name, Object defaultValue) |
| throws IOException, IllegalArgumentException; |
| } |
| |
| /** |
| * Constructs a new ObjectInputStream. This default constructor can be used |
| * by subclasses that do not want to use the public constructor if it |
| * allocates unneeded data. |
| * |
| * @throws IOException |
| * if an error occurs when creating this stream. |
| */ |
| protected ObjectInputStream() throws IOException { |
| // WARNING - we should throw IOException if not called from a subclass |
| // according to the JavaDoc. Add the test. |
| this.subclassOverridingImplementation = true; |
| } |
| |
| /** |
| * Constructs a new ObjectInputStream that reads from the InputStream |
| * {@code input}. |
| * |
| * @param input |
| * the non-null source InputStream to filter reads on. |
| * @throws IOException |
| * if an error occurs while reading the stream header. |
| * @throws StreamCorruptedException |
| * if the source stream does not contain serialized objects that |
| * can be read. |
| */ |
| public ObjectInputStream(InputStream input) throws StreamCorruptedException, IOException { |
| this.input = (input instanceof DataInputStream) |
| ? (DataInputStream) input : new DataInputStream(input); |
| primitiveTypes = new DataInputStream(this); |
| enableResolve = false; |
| this.subclassOverridingImplementation = false; |
| resetState(); |
| nestedLevels = 0; |
| // So read...() methods can be used by |
| // subclasses during readStreamHeader() |
| primitiveData = this.input; |
| // Has to be done here according to the specification |
| readStreamHeader(); |
| primitiveData = emptyStream; |
| } |
| |
| @Override |
| public int available() throws IOException { |
| // returns 0 if next data is an object, or N if reading primitive types |
| checkReadPrimitiveTypes(); |
| return primitiveData.available(); |
| } |
| |
| /** |
| * Checks to if it is ok to read primitive types from this stream at |
| * this point. One is not supposed to read primitive types when about to |
| * read an object, for example, so an exception has to be thrown. |
| * |
| * @throws IOException |
| * If any IO problem occurred when trying to read primitive type |
| * or if it is illegal to read primitive types |
| */ |
| private void checkReadPrimitiveTypes() throws IOException { |
| // If we still have primitive data, it is ok to read primitive data |
| if (primitiveData == input || primitiveData.available() > 0) { |
| return; |
| } |
| |
| // If we got here either we had no Stream previously created or |
| // we no longer have data in that one, so get more bytes |
| do { |
| int next = 0; |
| if (hasPushbackTC) { |
| hasPushbackTC = false; |
| } else { |
| next = input.read(); |
| pushbackTC = (byte) next; |
| } |
| switch (pushbackTC) { |
| case TC_BLOCKDATA: |
| primitiveData = new ByteArrayInputStream(readBlockData()); |
| return; |
| case TC_BLOCKDATALONG: |
| primitiveData = new ByteArrayInputStream(readBlockDataLong()); |
| return; |
| case TC_RESET: |
| resetState(); |
| break; |
| default: |
| if (next != -1) { |
| pushbackTC(); |
| } |
| return; |
| } |
| // Only TC_RESET falls through |
| } while (true); |
| } |
| |
| /** |
| * Closes this stream. This implementation closes the source stream. |
| * |
| * @throws IOException |
| * if an error occurs while closing this stream. |
| */ |
| @Override |
| public void close() throws IOException { |
| input.close(); |
| } |
| |
| /** |
| * Default method to read objects from this stream. Serializable fields |
| * defined in the object's class and superclasses are read from the source |
| * stream. |
| * |
| * @throws ClassNotFoundException |
| * if the object's class cannot be found. |
| * @throws IOException |
| * if an I/O error occurs while reading the object data. |
| * @throws NotActiveException |
| * if this method is not called from {@code readObject()}. |
| * @see ObjectOutputStream#defaultWriteObject |
| */ |
| public void defaultReadObject() throws IOException, ClassNotFoundException, |
| NotActiveException { |
| if (currentObject != null || !mustResolve) { |
| readFieldValues(currentObject, currentClass); |
| } else { |
| throw new NotActiveException(); |
| } |
| } |
| |
| /** |
| * Enables object replacement for this stream. By default this is not |
| * enabled. Only trusted subclasses (loaded with system class loader) are |
| * allowed to change this status. |
| * |
| * @param enable |
| * {@code true} to enable object replacement; {@code false} to |
| * disable it. |
| * @return the previous setting. |
| * @see #resolveObject |
| * @see ObjectOutputStream#enableReplaceObject |
| */ |
| protected boolean enableResolveObject(boolean enable) { |
| boolean originalValue = enableResolve; |
| enableResolve = enable; |
| return originalValue; |
| } |
| |
| /** |
| * Return the next {@code int} handle to be used to indicate cyclic |
| * references being loaded from the stream. |
| * |
| * @return the next handle to represent the next cyclic reference |
| */ |
| private int nextHandle() { |
| return nextHandle++; |
| } |
| |
| /** |
| * Return the next token code (TC) from the receiver, which indicates what |
| * kind of object follows |
| * |
| * @return the next TC from the receiver |
| * |
| * @throws IOException |
| * If an IO error occurs |
| * |
| * @see ObjectStreamConstants |
| */ |
| private byte nextTC() throws IOException { |
| if (hasPushbackTC) { |
| hasPushbackTC = false; // We are consuming it |
| } else { |
| // Just in case a later call decides to really push it back, |
| // we don't require the caller to pass it as parameter |
| pushbackTC = input.readByte(); |
| } |
| return pushbackTC; |
| } |
| |
| /** |
| * Pushes back the last TC code read |
| */ |
| private void pushbackTC() { |
| hasPushbackTC = true; |
| } |
| |
| /** |
| * Reads a single byte from the source stream and returns it as an integer |
| * in the range from 0 to 255. Returns -1 if the end of the source stream |
| * has been reached. Blocks if no input is available. |
| * |
| * @return the byte read or -1 if the end of the source stream has been |
| * reached. |
| * @throws IOException |
| * if an error occurs while reading from this stream. |
| */ |
| @Override |
| public int read() throws IOException { |
| checkReadPrimitiveTypes(); |
| return primitiveData.read(); |
| } |
| |
| @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { |
| Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount); |
| if (byteCount == 0) { |
| return 0; |
| } |
| checkReadPrimitiveTypes(); |
| return primitiveData.read(buffer, byteOffset, byteCount); |
| } |
| |
| /** |
| * Reads and returns an array of raw bytes with primitive data. The array |
| * will have up to 255 bytes. The primitive data will be in the format |
| * described by {@code DataOutputStream}. |
| * |
| * @return The primitive data read, as raw bytes |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the primitive data. |
| */ |
| private byte[] readBlockData() throws IOException { |
| byte[] result = new byte[input.readByte() & 0xff]; |
| input.readFully(result); |
| return result; |
| } |
| |
| /** |
| * Reads and returns an array of raw bytes with primitive data. The array |
| * will have more than 255 bytes. The primitive data will be in the format |
| * described by {@code DataOutputStream}. |
| * |
| * @return The primitive data read, as raw bytes |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the primitive data. |
| */ |
| private byte[] readBlockDataLong() throws IOException { |
| byte[] result = new byte[input.readInt()]; |
| input.readFully(result); |
| return result; |
| } |
| |
| /** |
| * Reads a boolean from the source stream. |
| * |
| * @return the boolean value read from the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public boolean readBoolean() throws IOException { |
| return primitiveTypes.readBoolean(); |
| } |
| |
| /** |
| * Reads a byte (8 bit) from the source stream. |
| * |
| * @return the byte value read from the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public byte readByte() throws IOException { |
| return primitiveTypes.readByte(); |
| } |
| |
| /** |
| * Reads a character (16 bit) from the source stream. |
| * |
| * @return the char value read from the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public char readChar() throws IOException { |
| return primitiveTypes.readChar(); |
| } |
| |
| /** |
| * Reads and discards block data and objects until TC_ENDBLOCKDATA is found. |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the optional class |
| * annotation. |
| * @throws ClassNotFoundException |
| * If the class corresponding to the class descriptor could not |
| * be found. |
| */ |
| private void discardData() throws ClassNotFoundException, IOException { |
| primitiveData = emptyStream; |
| boolean resolve = mustResolve; |
| mustResolve = false; |
| do { |
| byte tc = nextTC(); |
| if (tc == TC_ENDBLOCKDATA) { |
| mustResolve = resolve; |
| return; // End of annotation |
| } |
| readContent(tc); |
| } while (true); |
| } |
| |
| /** |
| * Reads a class descriptor (an {@code ObjectStreamClass}) from the |
| * stream. |
| * |
| * @return the class descriptor read from the stream |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the class |
| * descriptor. |
| * @throws ClassNotFoundException |
| * If the class corresponding to the class descriptor could not |
| * be found. |
| */ |
| private ObjectStreamClass readClassDesc() throws ClassNotFoundException, IOException { |
| byte tc = nextTC(); |
| switch (tc) { |
| case TC_CLASSDESC: |
| return readNewClassDesc(false); |
| case TC_PROXYCLASSDESC: |
| Class<?> proxyClass = readNewProxyClassDesc(); |
| ObjectStreamClass streamClass = ObjectStreamClass.lookup(proxyClass); |
| streamClass.setLoadFields(ObjectStreamClass.NO_FIELDS); |
| registerObjectRead(streamClass, nextHandle(), false); |
| checkedSetSuperClassDesc(streamClass, readClassDesc()); |
| return streamClass; |
| case TC_REFERENCE: |
| return (ObjectStreamClass) readCyclicReference(); |
| case TC_NULL: |
| return null; |
| default: |
| throw corruptStream(tc); |
| } |
| } |
| |
| private StreamCorruptedException corruptStream(byte tc) throws StreamCorruptedException { |
| throw new StreamCorruptedException("Wrong format: " + Integer.toHexString(tc & 0xff)); |
| } |
| |
| /** |
| * Reads the content of the receiver based on the previously read token |
| * {@code tc}. |
| * |
| * @param tc |
| * The token code for the next item in the stream |
| * @return the object read from the stream |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the class |
| * descriptor. |
| * @throws ClassNotFoundException |
| * If the class corresponding to the object being read could not |
| * be found. |
| */ |
| private Object readContent(byte tc) throws ClassNotFoundException, |
| IOException { |
| switch (tc) { |
| case TC_BLOCKDATA: |
| return readBlockData(); |
| case TC_BLOCKDATALONG: |
| return readBlockDataLong(); |
| case TC_CLASS: |
| return readNewClass(false); |
| case TC_CLASSDESC: |
| return readNewClassDesc(false); |
| case TC_ARRAY: |
| return readNewArray(false); |
| case TC_OBJECT: |
| return readNewObject(false); |
| case TC_STRING: |
| return readNewString(false); |
| case TC_LONGSTRING: |
| return readNewLongString(false); |
| case TC_REFERENCE: |
| return readCyclicReference(); |
| case TC_NULL: |
| return null; |
| case TC_EXCEPTION: |
| Exception exc = readException(); |
| throw new WriteAbortedException("Read an exception", exc); |
| case TC_RESET: |
| resetState(); |
| return null; |
| default: |
| throw corruptStream(tc); |
| } |
| } |
| |
| /** |
| * Reads the content of the receiver based on the previously read token |
| * {@code tc}. Primitive data content is considered an error. |
| * |
| * @param unshared |
| * read the object unshared |
| * @return the object read from the stream |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the class |
| * descriptor. |
| * @throws ClassNotFoundException |
| * If the class corresponding to the object being read could not |
| * be found. |
| */ |
| private Object readNonPrimitiveContent(boolean unshared) |
| throws ClassNotFoundException, IOException { |
| checkReadPrimitiveTypes(); |
| if (primitiveData.available() > 0) { |
| OptionalDataException e = new OptionalDataException(); |
| e.length = primitiveData.available(); |
| throw e; |
| } |
| |
| do { |
| byte tc = nextTC(); |
| switch (tc) { |
| case TC_CLASS: |
| return readNewClass(unshared); |
| case TC_CLASSDESC: |
| return readNewClassDesc(unshared); |
| case TC_ARRAY: |
| return readNewArray(unshared); |
| case TC_OBJECT: |
| return readNewObject(unshared); |
| case TC_STRING: |
| return readNewString(unshared); |
| case TC_LONGSTRING: |
| return readNewLongString(unshared); |
| case TC_ENUM: |
| return readEnum(unshared); |
| case TC_REFERENCE: |
| if (unshared) { |
| readNewHandle(); |
| throw new InvalidObjectException("Unshared read of back reference"); |
| } |
| return readCyclicReference(); |
| case TC_NULL: |
| return null; |
| case TC_EXCEPTION: |
| Exception exc = readException(); |
| throw new WriteAbortedException("Read an exception", exc); |
| case TC_RESET: |
| resetState(); |
| break; |
| case TC_ENDBLOCKDATA: // Can occur reading class annotation |
| pushbackTC(); |
| OptionalDataException e = new OptionalDataException(); |
| e.eof = true; |
| throw e; |
| default: |
| throw corruptStream(tc); |
| } |
| // Only TC_RESET falls through |
| } while (true); |
| } |
| |
| /** |
| * Reads the next item from the stream assuming it is a cyclic reference to |
| * an object previously read. Return the actual object previously read. |
| * |
| * @return the object previously read from the stream |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the class |
| * descriptor. |
| * @throws InvalidObjectException |
| * If the cyclic reference is not valid. |
| */ |
| private Object readCyclicReference() throws InvalidObjectException, IOException { |
| return registeredObjectRead(readNewHandle()); |
| } |
| |
| /** |
| * Reads a double (64 bit) from the source stream. |
| * |
| * @return the double value read from the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public double readDouble() throws IOException { |
| return primitiveTypes.readDouble(); |
| } |
| |
| /** |
| * Read the next item assuming it is an exception. The exception is not a |
| * regular instance in the object graph, but the exception instance that |
| * happened (if any) when dumping the original object graph. The set of seen |
| * objects will be reset just before and just after loading this exception |
| * object. |
| * <p> |
| * When exceptions are found normally in the object graph, they are loaded |
| * as a regular object, and not by this method. In that case, the set of |
| * "known objects" is not reset. |
| * |
| * @return the exception read |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the exception |
| * object. |
| * @throws ClassNotFoundException |
| * If a class could not be found when reading the object graph |
| * for the exception |
| * @throws OptionalDataException |
| * If optional data could not be found when reading the |
| * exception graph |
| * @throws WriteAbortedException |
| * If another exception was caused when dumping this exception |
| */ |
| private Exception readException() throws WriteAbortedException, |
| OptionalDataException, ClassNotFoundException, IOException { |
| |
| resetSeenObjects(); |
| |
| // Now we read the Throwable object that was saved |
| // WARNING - the grammar says it is a Throwable, but the |
| // WriteAbortedException constructor takes an Exception. So, we read an |
| // Exception from the stream |
| Exception exc = (Exception) readObject(); |
| |
| // We reset the receiver's state (the grammar has "reset" in normal |
| // font) |
| resetSeenObjects(); |
| return exc; |
| } |
| |
| /** |
| * Reads a collection of field descriptors (name, type name, etc) for the |
| * class descriptor {@code cDesc} (an {@code ObjectStreamClass}) |
| * |
| * @param cDesc |
| * The class descriptor (an {@code ObjectStreamClass}) |
| * for which to write field information |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the field |
| * descriptors. |
| * @throws ClassNotFoundException |
| * If a class for one of the field types could not be found |
| * |
| * @see #readObject() |
| */ |
| private void readFieldDescriptors(ObjectStreamClass cDesc) |
| throws ClassNotFoundException, IOException { |
| short numFields = input.readShort(); |
| ObjectStreamField[] fields = new ObjectStreamField[numFields]; |
| |
| // We set it now, but each element will be inserted in the array further |
| // down |
| cDesc.setLoadFields(fields); |
| |
| // Check ObjectOutputStream.writeFieldDescriptors |
| for (short i = 0; i < numFields; i++) { |
| char typecode = (char) input.readByte(); |
| String fieldName = input.readUTF(); |
| boolean isPrimType = ObjectStreamClass.isPrimitiveType(typecode); |
| String classSig; |
| if (isPrimType) { |
| classSig = String.valueOf(typecode); |
| } else { |
| // The spec says it is a UTF, but experience shows they dump |
| // this String using writeObject (unlike the field name, which |
| // is saved with writeUTF). |
| // And if resolveObject is enabled, the classSig may be modified |
| // so that the original class descriptor cannot be read |
| // properly, so it is disabled. |
| boolean old = enableResolve; |
| try { |
| enableResolve = false; |
| classSig = (String) readObject(); |
| } finally { |
| enableResolve = old; |
| } |
| } |
| |
| classSig = formatClassSig(classSig); |
| ObjectStreamField f = new ObjectStreamField(classSig, fieldName); |
| fields[i] = f; |
| } |
| } |
| |
| /* |
| * Format the class signature for ObjectStreamField, for example, |
| * "[L[Ljava.lang.String;;" is converted to "[Ljava.lang.String;" |
| */ |
| private static String formatClassSig(String classSig) { |
| int start = 0; |
| int end = classSig.length(); |
| |
| if (end <= 0) { |
| return classSig; |
| } |
| |
| while (classSig.startsWith("[L", start) |
| && classSig.charAt(end - 1) == ';') { |
| start += 2; |
| end--; |
| } |
| |
| if (start > 0) { |
| start -= 2; |
| end++; |
| return classSig.substring(start, end); |
| } |
| return classSig; |
| } |
| |
| /** |
| * Reads the persistent fields of the object that is currently being read |
| * from the source stream. The values read are stored in a GetField object |
| * that provides access to the persistent fields. This GetField object is |
| * then returned. |
| * |
| * @return the GetField object from which persistent fields can be accessed |
| * by name. |
| * @throws ClassNotFoundException |
| * if the class of an object being deserialized can not be |
| * found. |
| * @throws IOException |
| * if an error occurs while reading from this stream. |
| * @throws NotActiveException |
| * if this stream is currently not reading an object. |
| */ |
| public GetField readFields() throws IOException, ClassNotFoundException, NotActiveException { |
| if (currentObject == null) { |
| throw new NotActiveException(); |
| } |
| EmulatedFieldsForLoading result = new EmulatedFieldsForLoading(currentClass); |
| readFieldValues(result); |
| return result; |
| } |
| |
| /** |
| * Reads a collection of field values for the emulated fields |
| * {@code emulatedFields} |
| * |
| * @param emulatedFields |
| * an {@code EmulatedFieldsForLoading}, concrete subclass |
| * of {@code GetField} |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the field values. |
| * @throws InvalidClassException |
| * If an incompatible type is being assigned to an emulated |
| * field. |
| * @throws OptionalDataException |
| * If optional data could not be found when reading the |
| * exception graph |
| * |
| * @see #readFields |
| * @see #readObject() |
| */ |
| private void readFieldValues(EmulatedFieldsForLoading emulatedFields) |
| throws OptionalDataException, InvalidClassException, IOException { |
| EmulatedFields.ObjectSlot[] slots = emulatedFields.emulatedFields().slots(); |
| for (ObjectSlot element : slots) { |
| element.defaulted = false; |
| Class<?> type = element.field.getType(); |
| if (type == int.class) { |
| element.fieldValue = input.readInt(); |
| } else if (type == byte.class) { |
| element.fieldValue = input.readByte(); |
| } else if (type == char.class) { |
| element.fieldValue = input.readChar(); |
| } else if (type == short.class) { |
| element.fieldValue = input.readShort(); |
| } else if (type == boolean.class) { |
| element.fieldValue = input.readBoolean(); |
| } else if (type == long.class) { |
| element.fieldValue = input.readLong(); |
| } else if (type == float.class) { |
| element.fieldValue = input.readFloat(); |
| } else if (type == double.class) { |
| element.fieldValue = input.readDouble(); |
| } else { |
| // Either array or Object |
| try { |
| element.fieldValue = readObject(); |
| } catch (ClassNotFoundException cnf) { |
| // WARNING- Not sure this is the right thing to do. Write |
| // test case. |
| throw new InvalidClassException(cnf.toString()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Reads a collection of field values for the class descriptor |
| * {@code classDesc} (an {@code ObjectStreamClass}). The |
| * values will be used to set instance fields in object {@code obj}. |
| * This is the default mechanism, when emulated fields (an |
| * {@code GetField}) are not used. Actual values to load are stored |
| * directly into the object {@code obj}. |
| * |
| * @param obj |
| * Instance in which the fields will be set. |
| * @param classDesc |
| * A class descriptor (an {@code ObjectStreamClass}) |
| * defining which fields should be loaded. |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the field values. |
| * @throws InvalidClassException |
| * If an incompatible type is being assigned to an emulated |
| * field. |
| * @throws OptionalDataException |
| * If optional data could not be found when reading the |
| * exception graph |
| * @throws ClassNotFoundException |
| * If a class of an object being de-serialized can not be found |
| * |
| * @see #readFields |
| * @see #readObject() |
| */ |
| private void readFieldValues(Object obj, ObjectStreamClass classDesc) |
| throws OptionalDataException, ClassNotFoundException, IOException { |
| // Now we must read all fields and assign them to the receiver |
| ObjectStreamField[] fields = classDesc.getLoadFields(); |
| fields = (fields == null) ? ObjectStreamClass.NO_FIELDS : fields; |
| Class<?> declaringClass = classDesc.forClass(); |
| if (declaringClass == null && mustResolve) { |
| throw new ClassNotFoundException(classDesc.getName()); |
| } |
| |
| for (ObjectStreamField fieldDesc : fields) { |
| // checkAndGetReflectionField() can return null if it was not able to find the field or |
| // if it is transient or static. We still need to read the data and do the other |
| // checking... |
| Field field = classDesc.checkAndGetReflectionField(fieldDesc); |
| try { |
| Class<?> type = fieldDesc.getTypeInternal(); |
| if (type == byte.class) { |
| byte b = input.readByte(); |
| if (field != null) { |
| field.setByte(obj, b); |
| } |
| } else if (type == char.class) { |
| char c = input.readChar(); |
| if (field != null) { |
| field.setChar(obj, c); |
| } |
| } else if (type == double.class) { |
| double d = input.readDouble(); |
| if (field != null) { |
| field.setDouble(obj, d); |
| } |
| } else if (type == float.class) { |
| float f = input.readFloat(); |
| if (field != null) { |
| field.setFloat(obj, f); |
| } |
| } else if (type == int.class) { |
| int i = input.readInt(); |
| if (field != null) { |
| field.setInt(obj, i); |
| } |
| } else if (type == long.class) { |
| long j = input.readLong(); |
| if (field != null) { |
| field.setLong(obj, j); |
| } |
| } else if (type == short.class) { |
| short s = input.readShort(); |
| if (field != null) { |
| field.setShort(obj, s); |
| } |
| } else if (type == boolean.class) { |
| boolean z = input.readBoolean(); |
| if (field != null) { |
| field.setBoolean(obj, z); |
| } |
| } else { |
| Object toSet = fieldDesc.isUnshared() ? readUnshared() : readObject(); |
| if (toSet != null) { |
| // Get the field type from the local field rather than |
| // from the stream's supplied data. That's the field |
| // we'll be setting, so that's the one that needs to be |
| // validated. |
| String fieldName = fieldDesc.getName(); |
| ObjectStreamField localFieldDesc = classDesc.getField(fieldName); |
| Class<?> fieldType = localFieldDesc.getTypeInternal(); |
| Class<?> valueType = toSet.getClass(); |
| if (!fieldType.isAssignableFrom(valueType)) { |
| throw new ClassCastException(classDesc.getName() + "." + fieldName + " - " + fieldType + " not compatible with " + valueType); |
| } |
| if (field != null) { |
| field.set(obj, toSet); |
| } |
| } |
| } |
| } catch (IllegalAccessException iae) { |
| // ObjectStreamField should have called setAccessible(true). |
| throw new AssertionError(iae); |
| } catch (NoSuchFieldError ignored) { |
| } |
| } |
| } |
| |
| /** |
| * Reads a float (32 bit) from the source stream. |
| * |
| * @return the float value read from the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public float readFloat() throws IOException { |
| return primitiveTypes.readFloat(); |
| } |
| |
| /** |
| * Reads bytes from the source stream into the byte array {@code dst}. |
| * This method will block until {@code dst.length} bytes have been read. |
| * |
| * @param dst |
| * the array in which to store the bytes read. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public void readFully(byte[] dst) throws IOException { |
| primitiveTypes.readFully(dst); |
| } |
| |
| /** |
| * Reads {@code byteCount} bytes from the source stream into the byte array {@code dst}. |
| * |
| * @param dst |
| * the byte array in which to store the bytes read. |
| * @param offset |
| * the initial position in {@code dst} to store the bytes |
| * read from the source stream. |
| * @param byteCount |
| * the number of bytes to read. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public void readFully(byte[] dst, int offset, int byteCount) throws IOException { |
| primitiveTypes.readFully(dst, offset, byteCount); |
| } |
| |
| /** |
| * Walks the hierarchy of classes described by class descriptor |
| * {@code classDesc} and reads the field values corresponding to |
| * fields declared by the corresponding class descriptor. The instance to |
| * store field values into is {@code object}. If the class |
| * (corresponding to class descriptor {@code classDesc}) defines |
| * private instance method {@code readObject} it will be used to load |
| * field values. |
| * |
| * @param object |
| * Instance into which stored field values loaded. |
| * @param classDesc |
| * A class descriptor (an {@code ObjectStreamClass}) |
| * defining which fields should be loaded. |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the field values in |
| * the hierarchy. |
| * @throws ClassNotFoundException |
| * If a class for one of the field types could not be found |
| * @throws NotActiveException |
| * If {@code defaultReadObject} is called from the wrong |
| * context. |
| * |
| * @see #defaultReadObject |
| * @see #readObject() |
| */ |
| private void readHierarchy(Object object, ObjectStreamClass classDesc) |
| throws IOException, ClassNotFoundException, NotActiveException { |
| if (object == null && mustResolve) { |
| throw new NotActiveException(); |
| } |
| |
| List<ObjectStreamClass> streamClassList = classDesc.getHierarchy(); |
| if (object == null) { |
| for (ObjectStreamClass objectStreamClass : streamClassList) { |
| readObjectForClass(null, objectStreamClass); |
| } |
| } else { |
| List<Class<?>> superclasses = cachedSuperclasses.get(object.getClass()); |
| if (superclasses == null) { |
| superclasses = cacheSuperclassesFor(object.getClass()); |
| } |
| |
| int lastIndex = 0; |
| for (int i = 0, end = superclasses.size(); i < end; ++i) { |
| Class<?> superclass = superclasses.get(i); |
| int index = findStreamSuperclass(superclass, streamClassList, lastIndex); |
| if (index == -1) { |
| readObjectNoData(object, superclass, |
| ObjectStreamClass.lookupStreamClass(superclass)); |
| } else { |
| for (int j = lastIndex; j <= index; j++) { |
| readObjectForClass(object, streamClassList.get(j)); |
| } |
| lastIndex = index + 1; |
| } |
| } |
| } |
| } |
| |
| private HashMap<Class<?>, List<Class<?>>> cachedSuperclasses = new HashMap<Class<?>, List<Class<?>>>(); |
| |
| private List<Class<?>> cacheSuperclassesFor(Class<?> c) { |
| ArrayList<Class<?>> result = new ArrayList<Class<?>>(); |
| Class<?> nextClass = c; |
| while (nextClass != null) { |
| Class<?> testClass = nextClass.getSuperclass(); |
| if (testClass != null) { |
| result.add(0, nextClass); |
| } |
| nextClass = testClass; |
| } |
| cachedSuperclasses.put(c, result); |
| return result; |
| } |
| |
| private int findStreamSuperclass(Class<?> cl, List<ObjectStreamClass> classList, int lastIndex) { |
| for (int i = lastIndex, end = classList.size(); i < end; i++) { |
| ObjectStreamClass objCl = classList.get(i); |
| String forName = objCl.forClass().getName(); |
| |
| if (objCl.getName().equals(forName)) { |
| if (cl.getName().equals(objCl.getName())) { |
| return i; |
| } |
| } else { |
| // there was a class replacement |
| if (cl.getName().equals(forName)) { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| private void readObjectNoData(Object object, Class<?> cl, ObjectStreamClass classDesc) |
| throws ObjectStreamException { |
| if (!classDesc.isSerializable()) { |
| return; |
| } |
| if (classDesc.hasMethodReadObjectNoData()){ |
| final Method readMethod = classDesc.getMethodReadObjectNoData(); |
| try { |
| readMethod.invoke(object); |
| } catch (InvocationTargetException e) { |
| Throwable ex = e.getTargetException(); |
| if (ex instanceof RuntimeException) { |
| throw (RuntimeException) ex; |
| } else if (ex instanceof Error) { |
| throw (Error) ex; |
| } |
| throw (ObjectStreamException) ex; |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException(e.toString()); |
| } |
| } |
| |
| } |
| |
| private void readObjectForClass(Object object, ObjectStreamClass classDesc) |
| throws IOException, ClassNotFoundException, NotActiveException { |
| // Have to do this before calling defaultReadObject or anything that |
| // calls defaultReadObject |
| currentObject = object; |
| currentClass = classDesc; |
| |
| boolean hadWriteMethod = (classDesc.getFlags() & SC_WRITE_METHOD) != 0; |
| Class<?> targetClass = classDesc.forClass(); |
| |
| final Method readMethod; |
| if (targetClass == null || !mustResolve) { |
| readMethod = null; |
| } else { |
| readMethod = classDesc.getMethodReadObject(); |
| } |
| try { |
| if (readMethod != null) { |
| // We have to be able to fetch its value, even if it is private |
| readMethod.setAccessible(true); |
| try { |
| readMethod.invoke(object, this); |
| } catch (InvocationTargetException e) { |
| Throwable ex = e.getTargetException(); |
| if (ex instanceof ClassNotFoundException) { |
| throw (ClassNotFoundException) ex; |
| } else if (ex instanceof RuntimeException) { |
| throw (RuntimeException) ex; |
| } else if (ex instanceof Error) { |
| throw (Error) ex; |
| } |
| throw (IOException) ex; |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException(e.toString()); |
| } |
| } else { |
| defaultReadObject(); |
| } |
| if (hadWriteMethod) { |
| discardData(); |
| } |
| } finally { |
| // Cleanup, needs to run always so that we can later detect invalid |
| // calls to defaultReadObject |
| currentObject = null; // We did not set this, so we do not need to |
| // clean it |
| currentClass = null; |
| } |
| } |
| |
| /** |
| * Reads an integer (32 bit) from the source stream. |
| * |
| * @return the integer value read from the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public int readInt() throws IOException { |
| return primitiveTypes.readInt(); |
| } |
| |
| /** |
| * Reads the next line from the source stream. Lines are terminated by |
| * {@code '\r'}, {@code '\n'}, {@code "\r\n"} or an {@code EOF}. |
| * |
| * @return the string read from the source stream. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| * @deprecated Use {@link BufferedReader} instead. |
| */ |
| @Deprecated |
| public String readLine() throws IOException { |
| return primitiveTypes.readLine(); |
| } |
| |
| /** |
| * Reads a long (64 bit) from the source stream. |
| * |
| * @return the long value read from the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public long readLong() throws IOException { |
| return primitiveTypes.readLong(); |
| } |
| |
| /** |
| * Read a new array from the receiver. It is assumed the array has not been |
| * read yet (not a cyclic reference). Return the array read. |
| * |
| * @param unshared |
| * read the object unshared |
| * @return the array read |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the array. |
| * @throws ClassNotFoundException |
| * If a class for one of the objects could not be found |
| * @throws OptionalDataException |
| * If optional data could not be found when reading the array. |
| */ |
| private Object readNewArray(boolean unshared) throws OptionalDataException, |
| ClassNotFoundException, IOException { |
| ObjectStreamClass classDesc = readClassDesc(); |
| |
| if (classDesc == null) { |
| throw missingClassDescriptor(); |
| } |
| |
| int newHandle = nextHandle(); |
| |
| // Array size |
| int size = input.readInt(); |
| Class<?> arrayClass = classDesc.forClass(); |
| Class<?> componentType = arrayClass.getComponentType(); |
| Object result = Array.newInstance(componentType, size); |
| |
| registerObjectRead(result, newHandle, unshared); |
| |
| // Now we have code duplication just because Java is typed. We have to |
| // read N elements and assign to array positions, but we must typecast |
| // the array first, and also call different methods depending on the |
| // elements. |
| if (componentType.isPrimitive()) { |
| if (componentType == int.class) { |
| int[] intArray = (int[]) result; |
| for (int i = 0; i < size; i++) { |
| intArray[i] = input.readInt(); |
| } |
| } else if (componentType == byte.class) { |
| byte[] byteArray = (byte[]) result; |
| input.readFully(byteArray, 0, size); |
| } else if (componentType == char.class) { |
| char[] charArray = (char[]) result; |
| for (int i = 0; i < size; i++) { |
| charArray[i] = input.readChar(); |
| } |
| } else if (componentType == short.class) { |
| short[] shortArray = (short[]) result; |
| for (int i = 0; i < size; i++) { |
| shortArray[i] = input.readShort(); |
| } |
| } else if (componentType == boolean.class) { |
| boolean[] booleanArray = (boolean[]) result; |
| for (int i = 0; i < size; i++) { |
| booleanArray[i] = input.readBoolean(); |
| } |
| } else if (componentType == long.class) { |
| long[] longArray = (long[]) result; |
| for (int i = 0; i < size; i++) { |
| longArray[i] = input.readLong(); |
| } |
| } else if (componentType == float.class) { |
| float[] floatArray = (float[]) result; |
| for (int i = 0; i < size; i++) { |
| floatArray[i] = input.readFloat(); |
| } |
| } else if (componentType == double.class) { |
| double[] doubleArray = (double[]) result; |
| for (int i = 0; i < size; i++) { |
| doubleArray[i] = input.readDouble(); |
| } |
| } else { |
| throw new ClassNotFoundException("Wrong base type in " + classDesc.getName()); |
| } |
| } else { |
| // Array of Objects |
| Object[] objectArray = (Object[]) result; |
| for (int i = 0; i < size; i++) { |
| // TODO: This place is the opportunity for enhancement |
| // We can implement writing elements through fast-path, |
| // without setting up the context (see readObject()) for |
| // each element with public API |
| objectArray[i] = readObject(); |
| } |
| } |
| if (enableResolve) { |
| result = resolveObject(result); |
| registerObjectRead(result, newHandle, false); |
| } |
| return result; |
| } |
| |
| /** |
| * Reads a new class from the receiver. It is assumed the class has not been |
| * read yet (not a cyclic reference). Return the class read. |
| * |
| * @param unshared |
| * read the object unshared |
| * @return The {@code java.lang.Class} read from the stream. |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the class. |
| * @throws ClassNotFoundException |
| * If a class for one of the objects could not be found |
| */ |
| private Class<?> readNewClass(boolean unshared) throws ClassNotFoundException, IOException { |
| ObjectStreamClass classDesc = readClassDesc(); |
| if (classDesc == null) { |
| throw missingClassDescriptor(); |
| } |
| Class<?> localClass = classDesc.forClass(); |
| if (localClass != null) { |
| registerObjectRead(localClass, nextHandle(), unshared); |
| } |
| return localClass; |
| } |
| |
| /* |
| * read class type for Enum, note there's difference between enum and normal |
| * classes |
| */ |
| private ObjectStreamClass readEnumDesc() throws IOException, |
| ClassNotFoundException { |
| byte tc = nextTC(); |
| switch (tc) { |
| case TC_CLASSDESC: |
| return readEnumDescInternal(); |
| case TC_REFERENCE: |
| return (ObjectStreamClass) readCyclicReference(); |
| case TC_NULL: |
| return null; |
| default: |
| throw corruptStream(tc); |
| } |
| } |
| |
| private ObjectStreamClass readEnumDescInternal() throws IOException, ClassNotFoundException { |
| ObjectStreamClass classDesc; |
| primitiveData = input; |
| int oldHandle = descriptorHandle; |
| descriptorHandle = nextHandle(); |
| classDesc = readClassDescriptor(); |
| registerObjectRead(classDesc, descriptorHandle, false); |
| descriptorHandle = oldHandle; |
| primitiveData = emptyStream; |
| classDesc.setClass(resolveClass(classDesc)); |
| // Consume unread class annotation data and TC_ENDBLOCKDATA |
| discardData(); |
| ObjectStreamClass superClass = readClassDesc(); |
| checkedSetSuperClassDesc(classDesc, superClass); |
| // Check SUIDs, note all SUID for Enum is 0L |
| if (0L != classDesc.getSerialVersionUID() || 0L != superClass.getSerialVersionUID()) { |
| throw new InvalidClassException(superClass.getName(), |
| "Incompatible class (SUID): " + superClass + " but expected " + superClass); |
| } |
| byte tc = nextTC(); |
| // discard TC_ENDBLOCKDATA after classDesc if any |
| if (tc == TC_ENDBLOCKDATA) { |
| // read next parent class. For enum, it may be null |
| superClass.setSuperclass(readClassDesc()); |
| } else { |
| // not TC_ENDBLOCKDATA, push back for next read |
| pushbackTC(); |
| } |
| return classDesc; |
| } |
| |
| @SuppressWarnings("unchecked")// For the Enum.valueOf call |
| private Object readEnum(boolean unshared) throws OptionalDataException, |
| ClassNotFoundException, IOException { |
| // read classdesc for Enum first |
| ObjectStreamClass classDesc = readEnumDesc(); |
| |
| Class enumType = classDesc.checkAndGetTcEnumClass(); |
| |
| int newHandle = nextHandle(); |
| // read name after class desc |
| String name; |
| byte tc = nextTC(); |
| switch (tc) { |
| case TC_REFERENCE: |
| if (unshared) { |
| readNewHandle(); |
| throw new InvalidObjectException("Unshared read of back reference"); |
| } |
| name = (String) readCyclicReference(); |
| break; |
| case TC_STRING: |
| name = (String) readNewString(unshared); |
| break; |
| default: |
| throw corruptStream(tc); |
| } |
| |
| Enum<?> result; |
| try { |
| result = Enum.valueOf(enumType, name); |
| } catch (IllegalArgumentException e) { |
| InvalidObjectException ioe = new InvalidObjectException(e.getMessage()); |
| ioe.initCause(e); |
| throw ioe; |
| } |
| registerObjectRead(result, newHandle, unshared); |
| return result; |
| } |
| |
| /** |
| * Reads a new class descriptor from the receiver. It is assumed the class |
| * descriptor has not been read yet (not a cyclic reference). Return the |
| * class descriptor read. |
| * |
| * @param unshared |
| * read the object unshared |
| * @return The {@code ObjectStreamClass} read from the stream. |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the class |
| * descriptor. |
| * @throws ClassNotFoundException |
| * If a class for one of the objects could not be found |
| */ |
| private ObjectStreamClass readNewClassDesc(boolean unshared) |
| throws ClassNotFoundException, IOException { |
| // So read...() methods can be used by |
| // subclasses during readClassDescriptor() |
| primitiveData = input; |
| int oldHandle = descriptorHandle; |
| descriptorHandle = nextHandle(); |
| ObjectStreamClass newClassDesc = readClassDescriptor(); |
| registerObjectRead(newClassDesc, descriptorHandle, unshared); |
| descriptorHandle = oldHandle; |
| primitiveData = emptyStream; |
| |
| // We need to map classDesc to class. |
| try { |
| newClassDesc.setClass(resolveClass(newClassDesc)); |
| // Check SUIDs & base name of the class |
| verifyAndInit(newClassDesc); |
| } catch (ClassNotFoundException e) { |
| if (mustResolve) { |
| throw e; |
| // Just continue, the class may not be required |
| } |
| } |
| |
| // Resolve the field signatures using the class loader of the |
| // resolved class |
| ObjectStreamField[] fields = newClassDesc.getLoadFields(); |
| fields = (fields == null) ? ObjectStreamClass.NO_FIELDS : fields; |
| ClassLoader loader = newClassDesc.forClass() == null ? callerClassLoader |
| : newClassDesc.forClass().getClassLoader(); |
| for (ObjectStreamField element : fields) { |
| element.resolve(loader); |
| } |
| |
| // Consume unread class annotation data and TC_ENDBLOCKDATA |
| discardData(); |
| checkedSetSuperClassDesc(newClassDesc, readClassDesc()); |
| return newClassDesc; |
| } |
| |
| /** |
| * Reads a new proxy class descriptor from the receiver. It is assumed the |
| * proxy class descriptor has not been read yet (not a cyclic reference). |
| * Return the proxy class descriptor read. |
| * |
| * @return The {@code Class} read from the stream. |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the class |
| * descriptor. |
| * @throws ClassNotFoundException |
| * If a class for one of the objects could not be found |
| */ |
| private Class<?> readNewProxyClassDesc() throws ClassNotFoundException, |
| IOException { |
| int count = input.readInt(); |
| String[] interfaceNames = new String[count]; |
| for (int i = 0; i < count; i++) { |
| interfaceNames[i] = input.readUTF(); |
| } |
| Class<?> proxy = resolveProxyClass(interfaceNames); |
| // Consume unread class annotation data and TC_ENDBLOCKDATA |
| discardData(); |
| return proxy; |
| } |
| |
| /** |
| * Reads a class descriptor from the source stream. |
| * |
| * @return the class descriptor read from the source stream. |
| * @throws ClassNotFoundException |
| * if a class for one of the objects cannot be found. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { |
| ObjectStreamClass newClassDesc = new ObjectStreamClass(); |
| String name = input.readUTF(); |
| if (name.length() == 0) { |
| throw new IOException("The stream is corrupted"); |
| } |
| newClassDesc.setName(name); |
| newClassDesc.setSerialVersionUID(input.readLong()); |
| newClassDesc.setFlags(input.readByte()); |
| |
| /* |
| * We must register the class descriptor before reading field |
| * descriptors. If called outside of readObject, the descriptorHandle |
| * might be unset. |
| */ |
| if (descriptorHandle == -1) { |
| descriptorHandle = nextHandle(); |
| } |
| registerObjectRead(newClassDesc, descriptorHandle, false); |
| |
| readFieldDescriptors(newClassDesc); |
| return newClassDesc; |
| } |
| |
| /** |
| * Creates the proxy class that implements the interfaces specified in |
| * {@code interfaceNames}. |
| * |
| * @param interfaceNames |
| * the interfaces used to create the proxy class. |
| * @return the proxy class. |
| * @throws ClassNotFoundException |
| * if the proxy class or any of the specified interfaces cannot |
| * be created. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| * @see ObjectOutputStream#annotateProxyClass(Class) |
| */ |
| protected Class<?> resolveProxyClass(String[] interfaceNames) |
| throws IOException, ClassNotFoundException { |
| ClassLoader loader = callerClassLoader; |
| Class<?>[] interfaces = new Class<?>[interfaceNames.length]; |
| for (int i = 0; i < interfaceNames.length; i++) { |
| interfaces[i] = Class.forName(interfaceNames[i], false, loader); |
| } |
| try { |
| return Proxy.getProxyClass(loader, interfaces); |
| } catch (IllegalArgumentException e) { |
| throw new ClassNotFoundException(e.toString(), e); |
| } |
| } |
| |
| private int readNewHandle() throws IOException { |
| return input.readInt(); |
| } |
| |
| /** |
| * Read a new object from the stream. It is assumed the object has not been |
| * loaded yet (not a cyclic reference). Return the object read. |
| * |
| * If the object implements <code>Externalizable</code> its |
| * <code>readExternal</code> is called. Otherwise, all fields described by |
| * the class hierarchy are loaded. Each class can define how its declared |
| * instance fields are loaded by defining a private method |
| * <code>readObject</code> |
| * |
| * @param unshared |
| * read the object unshared |
| * @return the object read |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the object. |
| * @throws OptionalDataException |
| * If optional data could not be found when reading the object |
| * graph |
| * @throws ClassNotFoundException |
| * If a class for one of the objects could not be found |
| */ |
| private Object readNewObject(boolean unshared) |
| throws OptionalDataException, ClassNotFoundException, IOException { |
| ObjectStreamClass classDesc = readClassDesc(); |
| |
| if (classDesc == null) { |
| throw missingClassDescriptor(); |
| } |
| |
| Class<?> objectClass = classDesc.checkAndGetTcObjectClass(); |
| |
| int newHandle = nextHandle(); |
| Object result; |
| Object registeredResult = null; |
| if (objectClass != null) { |
| // Now we know which class to instantiate and which constructor to |
| // run. We are allowed to run the constructor. |
| result = classDesc.newInstance(objectClass); |
| registerObjectRead(result, newHandle, unshared); |
| registeredResult = result; |
| } else { |
| result = null; |
| } |
| |
| try { |
| // This is how we know what to do in defaultReadObject. And it is |
| // also used by defaultReadObject to check if it was called from an |
| // invalid place. It also allows readExternal to call |
| // defaultReadObject and have it work. |
| currentObject = result; |
| currentClass = classDesc; |
| |
| // If Externalizable, just let the object read itself |
| // Note that this value comes from the Stream, and in fact it could be |
| // that the classes have been changed so that the info below now |
| // conflicts with the newer class |
| boolean wasExternalizable = (classDesc.getFlags() & SC_EXTERNALIZABLE) != 0; |
| if (wasExternalizable) { |
| boolean blockData = (classDesc.getFlags() & SC_BLOCK_DATA) != 0; |
| if (!blockData) { |
| primitiveData = input; |
| } |
| if (mustResolve) { |
| Externalizable extern = (Externalizable) result; |
| extern.readExternal(this); |
| } |
| if (blockData) { |
| // Similar to readHierarchy. Anything not read by |
| // readExternal has to be consumed here |
| discardData(); |
| } else { |
| primitiveData = emptyStream; |
| } |
| } else { |
| // If we got here, it is Serializable but not Externalizable. |
| // Walk the hierarchy reading each class' slots |
| readHierarchy(result, classDesc); |
| } |
| } finally { |
| // Cleanup, needs to run always so that we can later detect invalid |
| // calls to defaultReadObject |
| currentObject = null; |
| currentClass = null; |
| } |
| |
| if (objectClass != null) { |
| |
| if (classDesc.hasMethodReadResolve()){ |
| Method methodReadResolve = classDesc.getMethodReadResolve(); |
| try { |
| result = methodReadResolve.invoke(result, (Object[]) null); |
| } catch (IllegalAccessException ignored) { |
| } catch (InvocationTargetException ite) { |
| Throwable target = ite.getTargetException(); |
| if (target instanceof ObjectStreamException) { |
| throw (ObjectStreamException) target; |
| } else if (target instanceof Error) { |
| throw (Error) target; |
| } else { |
| throw (RuntimeException) target; |
| } |
| } |
| |
| } |
| } |
| // We get here either if class-based replacement was not needed or if it |
| // was needed but produced the same object or if it could not be |
| // computed. |
| |
| // The object to return is the one we instantiated or a replacement for |
| // it |
| if (result != null && enableResolve) { |
| result = resolveObject(result); |
| } |
| if (registeredResult != result) { |
| registerObjectRead(result, newHandle, unshared); |
| } |
| return result; |
| } |
| |
| private InvalidClassException missingClassDescriptor() throws InvalidClassException { |
| throw new InvalidClassException("Read null attempting to read class descriptor for object"); |
| } |
| |
| /** |
| * Read a string encoded in {@link DataInput modified UTF-8} from the |
| * receiver. Return the string read. |
| * |
| * @param unshared |
| * read the object unshared |
| * @return the string just read. |
| * @throws IOException |
| * If an IO exception happened when reading the String. |
| */ |
| private Object readNewString(boolean unshared) throws IOException { |
| Object result = input.readUTF(); |
| if (enableResolve) { |
| result = resolveObject(result); |
| } |
| registerObjectRead(result, nextHandle(), unshared); |
| |
| return result; |
| } |
| |
| /** |
| * Read a new String in UTF format from the receiver. Return the string |
| * read. |
| * |
| * @param unshared |
| * read the object unshared |
| * @return the string just read. |
| * |
| * @throws IOException |
| * If an IO exception happened when reading the String. |
| */ |
| private Object readNewLongString(boolean unshared) throws IOException { |
| long length = input.readLong(); |
| Object result = input.decodeUTF((int) length); |
| if (enableResolve) { |
| result = resolveObject(result); |
| } |
| registerObjectRead(result, nextHandle(), unshared); |
| |
| return result; |
| } |
| |
| /** |
| * Reads the next object from the source stream. |
| * |
| * @return the object read from the source stream. |
| * @throws ClassNotFoundException |
| * if the class of one of the objects in the object graph cannot |
| * be found. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| * @throws OptionalDataException |
| * if primitive data types were found instead of an object. |
| * @see ObjectOutputStream#writeObject(Object) |
| */ |
| public final Object readObject() throws OptionalDataException, |
| ClassNotFoundException, IOException { |
| return readObject(false); |
| } |
| |
| /** |
| * Reads the next unshared object from the source stream. |
| * |
| * @return the new object read. |
| * @throws ClassNotFoundException |
| * if the class of one of the objects in the object graph cannot |
| * be found. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| * @see ObjectOutputStream#writeUnshared |
| */ |
| public Object readUnshared() throws IOException, ClassNotFoundException { |
| return readObject(true); |
| } |
| |
| private Object readObject(boolean unshared) throws OptionalDataException, |
| ClassNotFoundException, IOException { |
| boolean restoreInput = (primitiveData == input); |
| if (restoreInput) { |
| primitiveData = emptyStream; |
| } |
| |
| // This is the spec'ed behavior in JDK 1.2. Very bizarre way to allow |
| // behavior overriding. |
| if (subclassOverridingImplementation && !unshared) { |
| return readObjectOverride(); |
| } |
| |
| // If we still had primitive types to read, should we discard them |
| // (reset the primitiveTypes stream) or leave as is, so that attempts to |
| // read primitive types won't read 'past data' ??? |
| Object result; |
| try { |
| // We need this so we can tell when we are returning to the |
| // original/outside caller |
| if (++nestedLevels == 1) { |
| // Remember the caller's class loader |
| callerClassLoader = VMStack.getClosestUserClassLoader(); |
| } |
| |
| result = readNonPrimitiveContent(unshared); |
| if (restoreInput) { |
| primitiveData = input; |
| } |
| } finally { |
| // We need this so we can tell when we are returning to the |
| // original/outside caller |
| if (--nestedLevels == 0) { |
| // We are going to return to the original caller, perform |
| // cleanups. |
| // No more need to remember the caller's class loader |
| callerClassLoader = null; |
| } |
| } |
| |
| // Done reading this object. Is it time to return to the original |
| // caller? If so we need to perform validations first. |
| if (nestedLevels == 0 && validations != null) { |
| // We are going to return to the original caller. If validation is |
| // enabled we need to run them now and then cleanup the validation |
| // collection |
| try { |
| for (InputValidationDesc element : validations) { |
| element.validator.validateObject(); |
| } |
| } finally { |
| // Validations have to be renewed, since they are only called |
| // from readObject |
| validations = null; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Method to be overridden by subclasses to read the next object from the |
| * source stream. |
| * |
| * @return the object read from the source stream. |
| * @throws ClassNotFoundException |
| * if the class of one of the objects in the object graph cannot |
| * be found. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| * @throws OptionalDataException |
| * if primitive data types were found instead of an object. |
| * @see ObjectOutputStream#writeObjectOverride |
| */ |
| protected Object readObjectOverride() throws OptionalDataException, |
| ClassNotFoundException, IOException { |
| if (input == null) { |
| return null; |
| } |
| // Subclasses must override. |
| throw new IOException(); |
| } |
| |
| /** |
| * Reads a short (16 bit) from the source stream. |
| * |
| * @return the short value read from the source stream. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public short readShort() throws IOException { |
| return primitiveTypes.readShort(); |
| } |
| |
| /** |
| * Reads and validates the ObjectInputStream header from the source stream. |
| * |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| * @throws StreamCorruptedException |
| * if the source stream does not contain readable serialized |
| * objects. |
| */ |
| protected void readStreamHeader() throws IOException, StreamCorruptedException { |
| if (input.readShort() == STREAM_MAGIC |
| && input.readShort() == STREAM_VERSION) { |
| return; |
| } |
| throw new StreamCorruptedException(); |
| } |
| |
| /** |
| * Reads an unsigned byte (8 bit) from the source stream. |
| * |
| * @return the unsigned byte value read from the source stream packaged in |
| * an integer. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public int readUnsignedByte() throws IOException { |
| return primitiveTypes.readUnsignedByte(); |
| } |
| |
| /** |
| * Reads an unsigned short (16 bit) from the source stream. |
| * |
| * @return the unsigned short value read from the source stream packaged in |
| * an integer. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public int readUnsignedShort() throws IOException { |
| return primitiveTypes.readUnsignedShort(); |
| } |
| |
| /** |
| * Reads a string encoded in {@link DataInput modified UTF-8} from the |
| * source stream. |
| * |
| * @return the string encoded in {@link DataInput modified UTF-8} read from |
| * the source stream. |
| * @throws EOFException |
| * if the end of the input is reached before the read |
| * request can be satisfied. |
| * @throws IOException |
| * if an error occurs while reading from the source stream. |
| */ |
| public String readUTF() throws IOException { |
| return primitiveTypes.readUTF(); |
| } |
| |
| /** |
| * Returns the previously-read object corresponding to the given serialization handle. |
| * @throws InvalidObjectException |
| * If there is no previously-read object with this handle |
| */ |
| private Object registeredObjectRead(int handle) throws InvalidObjectException { |
| Object res = objectsRead.get(handle - ObjectStreamConstants.baseWireHandle); |
| if (res == UNSHARED_OBJ) { |
| throw new InvalidObjectException("Cannot read back reference to unshared object"); |
| } |
| return res; |
| } |
| |
| /** |
| * Associates a read object with the its serialization handle. |
| */ |
| private void registerObjectRead(Object obj, int handle, boolean unshared) throws IOException { |
| if (unshared) { |
| obj = UNSHARED_OBJ; |
| } |
| int index = handle - ObjectStreamConstants.baseWireHandle; |
| int size = objectsRead.size(); |
| // ObjectOutputStream sometimes wastes a handle. I've compared hex dumps of the RI |
| // and it seems like that's a 'feature'. Look for calls to objectsWritten.put that |
| // are guarded by !unshared tests. |
| while (index > size) { |
| objectsRead.add(null); |
| ++size; |
| } |
| if (index == size) { |
| objectsRead.add(obj); |
| } else { |
| objectsRead.set(index, obj); |
| } |
| } |
| |
| /** |
| * Registers a callback for post-deserialization validation of objects. It |
| * allows to perform additional consistency checks before the {@code |
| * readObject()} method of this class returns its result to the caller. This |
| * method can only be called from within the {@code readObject()} method of |
| * a class that implements "special" deserialization rules. It can be called |
| * multiple times. Validation callbacks are then done in order of decreasing |
| * priority, defined by {@code priority}. |
| * |
| * @param object |
| * an object that can validate itself by receiving a callback. |
| * @param priority |
| * the validator's priority. |
| * @throws InvalidObjectException |
| * if {@code object} is {@code null}. |
| * @throws NotActiveException |
| * if this stream is currently not reading objects. In that |
| * case, calling this method is not allowed. |
| * @see ObjectInputValidation#validateObject() |
| */ |
| public synchronized void registerValidation(ObjectInputValidation object, |
| int priority) throws NotActiveException, InvalidObjectException { |
| // Validation can only be registered when inside readObject calls |
| Object instanceBeingRead = this.currentObject; |
| |
| if (instanceBeingRead == null && nestedLevels == 0) { |
| throw new NotActiveException(); |
| } |
| if (object == null) { |
| throw new InvalidObjectException("Callback object cannot be null"); |
| } |
| // From now on it is just insertion in a SortedCollection. Since |
| // the Java class libraries don't provide that, we have to |
| // implement it from scratch here. |
| InputValidationDesc desc = new InputValidationDesc(); |
| desc.validator = object; |
| desc.priority = priority; |
| // No need for this, validateObject does not take a parameter |
| // desc.toValidate = instanceBeingRead; |
| if (validations == null) { |
| validations = new InputValidationDesc[1]; |
| validations[0] = desc; |
| } else { |
| int i = 0; |
| for (; i < validations.length; i++) { |
| InputValidationDesc validation = validations[i]; |
| // Sorted, higher priority first. |
| if (priority >= validation.priority) { |
| break; // Found the index where to insert |
| } |
| } |
| InputValidationDesc[] oldValidations = validations; |
| int currentSize = oldValidations.length; |
| validations = new InputValidationDesc[currentSize + 1]; |
| System.arraycopy(oldValidations, 0, validations, 0, i); |
| System.arraycopy(oldValidations, i, validations, i + 1, currentSize |
| - i); |
| validations[i] = desc; |
| } |
| } |
| |
| /** |
| * Reset the collection of objects already loaded by the receiver. |
| */ |
| private void resetSeenObjects() { |
| objectsRead = new ArrayList<Object>(); |
| nextHandle = baseWireHandle; |
| primitiveData = emptyStream; |
| } |
| |
| /** |
| * Reset the receiver. The collection of objects already read by the |
| * receiver is reset, and internal structures are also reset so that the |
| * receiver knows it is in a fresh clean state. |
| */ |
| private void resetState() { |
| resetSeenObjects(); |
| hasPushbackTC = false; |
| pushbackTC = 0; |
| // nestedLevels = 0; |
| } |
| |
| /** |
| * Loads the Java class corresponding to the class descriptor {@code |
| * osClass} that has just been read from the source stream. |
| * |
| * @param osClass |
| * an ObjectStreamClass read from the source stream. |
| * @return a Class corresponding to the descriptor {@code osClass}. |
| * @throws ClassNotFoundException |
| * if the class for an object cannot be found. |
| * @throws IOException |
| * if an I/O error occurs while creating the class. |
| * @see ObjectOutputStream#annotateClass(Class) |
| */ |
| protected Class<?> resolveClass(ObjectStreamClass osClass) |
| throws IOException, ClassNotFoundException { |
| // fastpath: obtain cached value |
| Class<?> cls = osClass.forClass(); |
| if (cls == null) { |
| // slowpath: resolve the class |
| String className = osClass.getName(); |
| |
| // if it is primitive class, for example, long.class |
| cls = PRIMITIVE_CLASSES.get(className); |
| |
| if (cls == null) { |
| // not primitive class |
| cls = Class.forName(className, false, callerClassLoader); |
| } |
| } |
| return cls; |
| } |
| |
| /** |
| * Allows trusted subclasses to substitute the specified original {@code |
| * object} with a new object. Object substitution has to be activated first |
| * with calling {@code enableResolveObject(true)}. This implementation just |
| * returns {@code object}. |
| * |
| * @param object |
| * the original object for which a replacement may be defined. |
| * @return the replacement object for {@code object}. |
| * @throws IOException |
| * if any I/O error occurs while creating the replacement |
| * object. |
| * @see #enableResolveObject |
| * @see ObjectOutputStream#enableReplaceObject |
| * @see ObjectOutputStream#replaceObject |
| */ |
| protected Object resolveObject(Object object) throws IOException { |
| // By default no object replacement. Subclasses can override |
| return object; |
| } |
| |
| /** |
| * Skips {@code length} bytes on the source stream. This method should not |
| * be used to skip bytes at any arbitrary position, just when reading |
| * primitive data types (int, char etc). |
| * |
| * @param length |
| * the number of bytes to skip. |
| * @return the number of bytes actually skipped. |
| * @throws IOException |
| * if an error occurs while skipping bytes on the source stream. |
| * @throws NullPointerException |
| * if the source stream is {@code null}. |
| */ |
| public int skipBytes(int length) throws IOException { |
| // To be used with available. Ok to call if reading primitive buffer |
| if (input == null) { |
| throw new NullPointerException("source stream is null"); |
| } |
| |
| int offset = 0; |
| while (offset < length) { |
| checkReadPrimitiveTypes(); |
| long skipped = primitiveData.skip(length - offset); |
| if (skipped == 0) { |
| return offset; |
| } |
| offset += (int) skipped; |
| } |
| return length; |
| } |
| |
| /** |
| * Verify if the SUID & the base name for descriptor |
| * <code>loadedStreamClass</code>matches |
| * the SUID & the base name of the corresponding loaded class and |
| * init private fields. |
| * |
| * @param loadedStreamClass |
| * An ObjectStreamClass that was loaded from the stream. |
| * |
| * @throws InvalidClassException |
| * If the SUID of the stream class does not match the VM class |
| */ |
| private void verifyAndInit(ObjectStreamClass loadedStreamClass) |
| throws InvalidClassException { |
| |
| Class<?> localClass = loadedStreamClass.forClass(); |
| ObjectStreamClass localStreamClass = ObjectStreamClass.lookupStreamClass(localClass); |
| |
| if (loadedStreamClass.getSerialVersionUID() != localStreamClass |
| .getSerialVersionUID()) { |
| throw new InvalidClassException(loadedStreamClass.getName(), |
| "Incompatible class (SUID): " + loadedStreamClass + |
| " but expected " + localStreamClass); |
| } |
| |
| String loadedClassBaseName = getBaseName(loadedStreamClass.getName()); |
| String localClassBaseName = getBaseName(localStreamClass.getName()); |
| |
| if (!loadedClassBaseName.equals(localClassBaseName)) { |
| throw new InvalidClassException(loadedStreamClass.getName(), |
| String.format("Incompatible class (base name): %s but expected %s", |
| loadedClassBaseName, localClassBaseName)); |
| } |
| |
| loadedStreamClass.initPrivateFields(localStreamClass); |
| } |
| |
| private static String getBaseName(String fullName) { |
| int k = fullName.lastIndexOf('.'); |
| |
| if (k == -1 || k == (fullName.length() - 1)) { |
| return fullName; |
| } |
| return fullName.substring(k + 1); |
| } |
| |
| // Avoid recursive defining. |
| private static void checkedSetSuperClassDesc(ObjectStreamClass desc, |
| ObjectStreamClass superDesc) throws StreamCorruptedException { |
| if (desc.equals(superDesc)) { |
| throw new StreamCorruptedException(); |
| } |
| desc.setSuperclass(superDesc); |
| } |
| } |