/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed 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. */ /* * This file also contains elements from the openjdk. * * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.jboss.marshalling.cloner; import static java.lang.System.getSecurityManager; import static java.security.AccessController.doPrivileged; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.Externalizable; import java.io.File; import java.io.IOException; import java.io.InterruptedIOException; import java.io.InvalidObjectException; import java.io.NotActiveException; import java.io.NotSerializableException; import java.io.ObjectInputValidation; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.math.BigDecimal; import java.math.BigInteger; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.AccessControlContext; import java.security.AccessController; import java.util.ArrayDeque; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.regex.Pattern; import org.jboss.marshalling.AbstractObjectInput; import org.jboss.marshalling.AbstractObjectOutput; import org.jboss.marshalling.ByteInput; import org.jboss.marshalling.ByteOutput; import org.jboss.marshalling.Marshaller; import org.jboss.marshalling.MarshallerObjectInputStream; import org.jboss.marshalling.MarshallerObjectOutputStream; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.ObjectResolver; import org.jboss.marshalling.SerializabilityChecker; import org.jboss.marshalling.Unmarshaller; import org.jboss.marshalling._private.GetDeclaredFieldAction; import org.jboss.marshalling._private.GetUnsafeAction; import org.jboss.marshalling.reflect.SerializableClass; import org.jboss.marshalling.reflect.SerializableClassRegistry; import org.jboss.marshalling.reflect.SerializableField; import org.jboss.marshalling.util.BooleanReadField; import org.jboss.marshalling.util.ByteReadField; import org.jboss.marshalling.util.CharReadField; import org.jboss.marshalling.util.DoubleReadField; import org.jboss.marshalling.util.FloatReadField; import org.jboss.marshalling.util.IdentityIntMap; import org.jboss.marshalling.util.IntReadField; import org.jboss.marshalling.util.Kind; import org.jboss.marshalling.util.LongReadField; import org.jboss.marshalling.util.ObjectReadField; import org.jboss.marshalling.util.ReadField; import org.jboss.marshalling.util.ShortReadField; import sun.misc.Unsafe; /** * An object cloner which uses serialization methods to clone objects. */ class SerializingCloner implements ObjectCloner { private final CloneTable delegate; private final ObjectResolver objectResolver; private final ObjectResolver objectPreResolver; private final ClassCloner classCloner; private final SerializabilityChecker serializabilityChecker; private final int bufferSize; private final SerializableClassRegistry registry; /** * Create a new instance. * * @param configuration the configuration to use */ SerializingCloner(final ClonerConfiguration configuration) { final CloneTable delegate = configuration.getCloneTable(); this.delegate = delegate == null ? CloneTable.NULL : delegate; final ObjectResolver objectResolver = configuration.getObjectResolver(); this.objectResolver = objectResolver == null ? Marshalling.nullObjectResolver() : objectResolver; final ObjectResolver objectPreResolver = configuration.getObjectPreResolver(); this.objectPreResolver = objectPreResolver == null ? Marshalling.nullObjectResolver() : objectPreResolver; final ClassCloner classCloner = configuration.getClassCloner(); this.classCloner = classCloner == null ? ClassCloner.IDENTITY : classCloner; final SerializabilityChecker serializabilityChecker = configuration.getSerializabilityChecker(); this.serializabilityChecker = serializabilityChecker == null ? SerializabilityChecker.DEFAULT : serializabilityChecker; final int bufferSize = configuration.getBufferSize(); this.bufferSize = bufferSize < 1 ? 8192 : bufferSize; registry = SerializableClassRegistry.getInstance(); } private final IdentityHashMap clones = new IdentityHashMap(); public void reset() { synchronized (this) { clones.clear(); } } public Object clone(final Object orig) throws IOException, ClassNotFoundException { synchronized (this) { boolean ok = false; try { final Object clone = clone(orig, true); ok = true; return clone; } finally { if (! ok) { reset(); } } } } private Object clone(final Object orig, final boolean replace) throws IOException, ClassNotFoundException { if (orig == null) { return null; } if (Thread.currentThread().isInterrupted()) { throw new InterruptedIOException("Thread interrupted during cloning process"); } final IdentityHashMap clones = this.clones; Object cached = clones.get(orig); if (cached != null) { return cached; } Object replaced = orig; replaced = objectPreResolver.writeReplace(replaced); final ClassCloner classCloner = this.classCloner; if (replaced instanceof Class) { final Class classObj = (Class) replaced; final Class clonedClass = Proxy.isProxyClass(classObj) ? classCloner.cloneProxy(classObj) : classCloner.clone(classObj); clones.put(replaced, clonedClass); return clonedClass; } if ((cached = delegate.clone(replaced, this, classCloner)) != null) { clones.put(replaced, cached); return cached; } final Class objClass = replaced.getClass(); final SerializableClass info = registry.lookup(objClass); if (replace) { if (info.hasWriteReplace()) { replaced = info.callWriteReplace(replaced); } replaced = objectResolver.writeReplace(replaced); if (replaced != orig) { Object clone = clone(replaced, false); clones.put(orig, clone); return clone; } } final Class clonedClass = (Class) clone(objClass); final boolean sameClass = objClass == clonedClass; if (orig instanceof Enum) { if (sameClass) { // same class means same enum constants return orig; } else { final Class cloneEnumClass; //the actual object class may be a sub class of the enum class final Class enumClass = ((Enum) orig).getDeclaringClass(); if(enumClass == objClass) { cloneEnumClass = clonedClass.asSubclass(Enum.class); } else{ cloneEnumClass = ((Class)clone(enumClass)).asSubclass(Enum.class); } return Enum.valueOf(cloneEnumClass, ((Enum) orig).name()); } } if (Proxy.isProxyClass(objClass)) { return Proxy.newProxyInstance(clonedClass.getClassLoader(), clonedClass.getInterfaces(), (InvocationHandler) clone(getInvocationHandler(orig))); } if (UNCLONED.contains(objClass)) { return orig; } if (objClass.isArray()) { Object simpleClone = simpleClone(orig, objClass); if (simpleClone != null) return simpleClone; // must be an object array final Object[] origArray = (Object[]) orig; final int len = origArray.length; if (sameClass && len == 0) { clones.put(orig, orig); return orig; } if (UNCLONED.contains(objClass.getComponentType())) { final Object[] clone = origArray.clone(); clones.put(orig, clone); return clone; } final Object[] clone; if (sameClass) { clone = origArray.clone(); } else { clone = (Object[])Array.newInstance(clonedClass.getComponentType(), len); } // deep clone clones.put(orig, clone); for (int i = 0; i < len; i++) { clone[i] = clone(origArray[i]); } return clone; } final SerializableClass cloneInfo = sameClass ? info : registry.lookup(clonedClass); // Now check the serializable types final Object clone; if (orig instanceof Externalizable) { final Externalizable externalizable = (Externalizable) orig; clone = cloneInfo.callNoArgConstructor(); clones.put(orig, clone); final Queue steps = new ArrayDeque(); final StepObjectOutput soo = new StepObjectOutput(steps); externalizable.writeExternal(soo); soo.doFinish(); ((Externalizable) clone).readExternal(new StepObjectInput(steps)); } else if (serializabilityChecker.isSerializable(clonedClass)) { Class nonSerializable; for (nonSerializable = clonedClass.getSuperclass(); serializabilityChecker.isSerializable(nonSerializable); nonSerializable = nonSerializable.getSuperclass()) { if (nonSerializable == Object.class) break; } clone = cloneInfo.callNonInitConstructor(nonSerializable); final Class cloneClass = clone.getClass(); if (! (serializabilityChecker.isSerializable(cloneClass))) { throw new NotSerializableException(cloneClass.getName()); } clones.put(orig, clone); initSerializableClone(orig, info, clone, cloneInfo); } else { throw new NotSerializableException(objClass.getName()); } replaced = clone; if (cloneInfo.hasReadResolve()) { replaced = cloneInfo.callReadResolve(replaced); } replaced = objectPreResolver.readResolve(objectResolver.readResolve(replaced)); if (replaced != clone) clones.put(orig, replaced); return replaced; } private void initSerializableClone(final Object orig, final SerializableClass origInfo, final Object clone, final SerializableClass cloneInfo) throws IOException, ClassNotFoundException { final Class cloneClass = cloneInfo.getSubjectClass(); if (! serializabilityChecker.isSerializable(cloneClass)) { throw new NotSerializableException(cloneClass.getName()); } final Class cloneSuperClass = cloneClass.getSuperclass(); final Class origClass = origInfo.getSubjectClass(); // first, init the serializable superclass, if any final Class origSuperClass = origClass.getSuperclass(); if (serializabilityChecker.isSerializable(origSuperClass) || serializabilityChecker.isSerializable(cloneSuperClass)) { initSerializableClone(orig, registry.lookup(origSuperClass), clone, registry.lookup(cloneSuperClass)); } if (cloneClass != origClass && cloneClass != clone(origClass)) { if (cloneInfo.hasReadObjectNoData()) { cloneInfo.callReadObjectNoData(clone); } return; } if (! serializabilityChecker.isSerializable(origClass)) { if (cloneInfo.hasReadObjectNoData()) { cloneInfo.callReadObjectNoData(clone); } return; } final ClonerPutField fields = new ClonerPutField(); fields.defineFields(origInfo); if (origInfo.hasWriteObject()) { final Queue steps = new ArrayDeque(); final StepObjectOutputStream stepObjectOutputStream = createStepObjectOutputStream(orig, fields, steps); origInfo.callWriteObject(orig, stepObjectOutputStream); stepObjectOutputStream.flush(); stepObjectOutputStream.doFinish(); cloneFields(fields); if (cloneInfo.hasReadObject()) { StepObjectInputStream sois; cloneInfo.callReadObject(clone, sois = createStepObjectInputStream(clone, cloneInfo, fields, steps)); sois.doCallbacks(); } else { storeFields(cloneInfo, clone, fields); } } else { prepareFields(orig, fields); cloneFields(fields); if (cloneInfo.hasReadObject()) { StepObjectInputStream sois; cloneInfo.callReadObject(clone, sois = createStepObjectInputStream(clone, cloneInfo, fields, new ArrayDeque())); sois.doCallbacks(); } else { storeFields(cloneInfo, clone, fields); } } } private StepObjectInputStream createStepObjectInputStream(final Object clone, final SerializableClass cloneInfo, final ClonerPutField fields, final Queue steps) throws IOException { try { return getSecurityManager() == null ? new StepObjectInputStream(steps, fields, clone, cloneInfo) : doPrivileged(new PrivilegedExceptionAction() { public StepObjectInputStream run() throws Exception { return new StepObjectInputStream(steps, fields, clone, cloneInfo); } }); } catch (PrivilegedActionException e) { try { throw e.getCause(); } catch (IOException ioe) { throw ioe; } catch (RuntimeException re) { throw re; } catch (Error er) { throw er; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } } private StepObjectOutputStream createStepObjectOutputStream(final Object orig, final ClonerPutField fields, final Queue steps) throws IOException { try { return getSecurityManager() == null ? new StepObjectOutputStream(steps, fields, orig) : doPrivileged(new PrivilegedExceptionAction() { public StepObjectOutputStream run() throws IOException { return new StepObjectOutputStream(steps, fields, orig); } }); } catch (PrivilegedActionException e) { try { throw e.getCause(); } catch (IOException ioe) { throw ioe; } catch (RuntimeException re) { throw re; } catch (Error er) { throw er; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } } private void prepareFields(final Object subject, final ClonerPutField fields) throws InvalidObjectException { final Map defMap = fields.fieldDefMap; final Map map = fields.fieldMap; for (String name : defMap.keySet()) { final SerializableField serializableField = defMap.get(name); if (serializableField.isAccessible()) switch (serializableField.getKind()) { case BOOLEAN: map.put(name, new BooleanReadField(serializableField, serializableField.getBoolean(subject))); continue; case BYTE: map.put(name, new ByteReadField(serializableField, serializableField.getByte(subject))); continue; case CHAR: map.put(name, new CharReadField(serializableField, serializableField.getChar(subject))); continue; case DOUBLE: map.put(name, new DoubleReadField(serializableField, serializableField.getDouble(subject))); continue; case FLOAT: map.put(name, new FloatReadField(serializableField, serializableField.getFloat(subject))); continue; case INT: map.put(name, new IntReadField(serializableField, serializableField.getInt(subject))); continue; case LONG: map.put(name, new LongReadField(serializableField, serializableField.getLong(subject))); continue; case OBJECT: map.put(name, new ObjectReadField(serializableField, serializableField.getObject(subject))); continue; case SHORT: map.put(name, new ShortReadField(serializableField, serializableField.getShort(subject))); continue; default: throw new IllegalStateException(); } } } private void cloneFields(final ClonerPutField fields) throws IOException, ClassNotFoundException { final Map defMap = fields.fieldDefMap; final Map map = fields.fieldMap; for (String name : defMap.keySet()) { final SerializableField field = defMap.get(name); // only clone object field if it has been serialized if (field.getKind() == Kind.OBJECT && map.get(name) != null) { map.put(name, new ObjectReadField(field, clone(map.get(name).getObject()))); continue; } } } private void storeFields(final SerializableClass cloneInfo, final Object clone, final ClonerPutField fields) throws IOException { final Map map = fields.fieldMap; for (SerializableField cloneField : cloneInfo.getFields()) { final String name = cloneField.getName(); final ReadField field = map.get(name); if (cloneField.isAccessible()) switch (cloneField.getKind()) { case BOOLEAN: cloneField.setBoolean(clone, field == null ? false : field.getBoolean()); continue; case BYTE: cloneField.setByte(clone, field == null ? 0 : field.getByte()); continue; case CHAR: cloneField.setChar(clone, field == null ? 0 : field.getChar()); continue; case DOUBLE: cloneField.setDouble(clone, field == null ? 0 : field.getDouble()); continue; case FLOAT: cloneField.setFloat(clone, field == null ? 0 : field.getFloat()); continue; case INT: cloneField.setInt(clone, field == null ? 0 : field.getInt()); continue; case LONG: cloneField.setLong(clone, field == null ? 0 : field.getLong()); continue; case OBJECT: cloneField.setObject(clone, field == null ? null : field.getObject()); continue; case SHORT: cloneField.setShort(clone, field == null ? 0 : field.getShort()); continue; default: throw new IllegalStateException(); } } } private static Object simpleClone(final Object orig, final Class objClass) { final int idx = PRIMITIVE_ARRAYS.get(objClass, -1); switch (idx) { case 0: { final boolean[] booleans = (boolean[]) orig; return booleans.length == 0 ? orig : booleans.clone(); } case 1: { final byte[] bytes = (byte[]) orig; return bytes.length == 0 ? orig : bytes.clone(); } case 2: { final short[] shorts = (short[]) orig; return shorts.length == 0 ? orig : shorts.clone(); } case 3: { final int[] ints = (int[]) orig; return ints.length == 0 ? orig : ints.clone(); } case 4: { final long[] longs = (long[]) orig; return longs.length == 0 ? orig : longs.clone(); } case 5: { final float[] floats = (float[]) orig; return floats.length == 0 ? orig : floats.clone(); } case 6: { final double[] doubles = (double[]) orig; return doubles.length == 0 ? orig : doubles.clone(); } case 7: { final char[] chars = (char[]) orig; return chars.length == 0 ? orig : chars.clone(); } default: return null; // fall out } } private static InvocationHandler getInvocationHandler(final Object orig) { return (InvocationHandler) unsafe.getObjectVolatile(orig, proxyInvocationHandlerOffset); } private abstract static class Step { } private static final Set> UNCLONED; private static final IdentityIntMap> PRIMITIVE_ARRAYS; private static final Unsafe unsafe; private static final Field proxyInvocationHandler; private static final long proxyInvocationHandlerOffset; static { final Set> set = new HashSet>(); // List of final, immutable, serializable JDK classes with no external references to mutable items // These items can be sent back by reference and the caller will never know set.add(Boolean.class); set.add(Byte.class); set.add(Short.class); set.add(Integer.class); set.add(Long.class); set.add(Float.class); set.add(Double.class); set.add(Character.class); set.add(String.class); set.add(StackTraceElement.class); set.add(BigInteger.class); set.add(BigDecimal.class); set.add(Pattern.class); set.add(File.class); set.add(Collections.emptyList().getClass()); set.add(Collections.emptySet().getClass()); set.add(Collections.emptyMap().getClass()); UNCLONED = set; final IdentityIntMap> map = new IdentityIntMap>(); // List of cloneable, non-extensible, serializable JDK classes with no external references to mutable items // These items can be deeply cloned by a single method call, without worrying about the classloader map.put(boolean[].class, 0); map.put(byte[].class, 1); map.put(short[].class, 2); map.put(int[].class, 3); map.put(long[].class, 4); map.put(float[].class, 5); map.put(double[].class, 6); map.put(char[].class, 7); PRIMITIVE_ARRAYS = map; final Field field; final SecurityManager sm = getSecurityManager(); field = sm == null ? new GetDeclaredFieldAction(Proxy.class, "h").run() : doPrivileged(new GetDeclaredFieldAction(Proxy.class, "h")); unsafe = sm == null ? GetUnsafeAction.INSTANCE.run() : doPrivileged(GetUnsafeAction.INSTANCE); proxyInvocationHandler = field; proxyInvocationHandlerOffset = unsafe.objectFieldOffset(proxyInvocationHandler); } class StepObjectOutput extends AbstractObjectOutput implements Marshaller { private final Queue steps; private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); StepObjectOutput(final Queue steps) throws IOException { super(SerializingCloner.this.bufferSize); this.steps = steps; super.start(Marshalling.createByteOutput(byteArrayOutputStream)); } protected void doWriteObject(final Object obj, final boolean unshared) throws IOException { super.flush(); final ByteArrayOutputStream baos = byteArrayOutputStream; if (baos.size() > 0) { steps.add(new ByteDataStep(baos.toByteArray())); baos.reset(); } steps.add(new CloneStep(obj)); } public void clearInstanceCache() throws IOException { throw new UnsupportedOperationException(); } public void clearClassCache() throws IOException { throw new UnsupportedOperationException(); } public void start(final ByteOutput byteOutput) throws IOException { throw new UnsupportedOperationException(); } public void finish() throws IOException { throw new UnsupportedOperationException(); } void doFinish() throws IOException { super.finish(); } public void flush() throws IOException { super.flush(); final ByteArrayOutputStream baos = byteArrayOutputStream; if (baos.size() > 0) { steps.add(new ByteDataStep(baos.toByteArray())); baos.reset(); } } } class StepObjectOutputStream extends MarshallerObjectOutputStream { private final Queue steps; private final ClonerPutField clonerPutField; private final Object subject; private final StepObjectOutput output; private StepObjectOutputStream(StepObjectOutput output, final Queue steps, final ClonerPutField clonerPutField, final Object subject) throws IOException { super(output); this.output = output; this.steps = steps; this.clonerPutField = clonerPutField; this.subject = subject; } StepObjectOutputStream(final Queue steps, final ClonerPutField clonerPutField, final Object subject) throws IOException { this(new StepObjectOutput(steps), steps, clonerPutField, subject); } public void writeFields() throws IOException { if (! steps.isEmpty()) { throw new IllegalStateException("writeFields may not be called in this context"); } } public PutField putFields() throws IOException { if (! steps.isEmpty()) { throw new IllegalStateException("putFields may not be called in this context"); } return clonerPutField; } public void defaultWriteObject() throws IOException { if (! steps.isEmpty()) { throw new IllegalStateException("defaultWriteObject may not be called in this context"); } final Object subject = this.subject; final SerializingCloner.ClonerPutField fields = clonerPutField; prepareFields(subject, fields); } void doFinish() throws IOException { output.doFinish(); } } /** * Prioritized list of callbacks to be performed once object graph has been * completely deserialized. */ private static class ValidationList { private static class Callback { final ObjectInputValidation obj; final int priority; Callback next; final AccessControlContext acc; Callback(ObjectInputValidation obj, int priority, Callback next, AccessControlContext acc) { this.obj = obj; this.priority = priority; this.next = next; this.acc = acc; } } /** linked list of callbacks */ private Callback list; /** * Creates new (empty) ValidationList. */ ValidationList() { } /** * Registers callback. Throws InvalidObjectException if callback * object is null. */ void register(ObjectInputValidation obj, int priority) throws InvalidObjectException { if (obj == null) { throw new InvalidObjectException("null callback"); } Callback prev = null, cur = list; while (cur != null && priority < cur.priority) { prev = cur; cur = cur.next; } AccessControlContext acc = AccessController.getContext(); if (prev != null) { prev.next = new Callback(obj, priority, cur, acc); } else { list = new Callback(obj, priority, list, acc); } } /** * Invokes all registered callbacks and clears the callback list. * Callbacks with higher priorities are called first; those with equal * priorities may be called in any order. If any of the callbacks * throws an InvalidObjectException, the callback process is terminated * and the exception propagated upwards. */ void doCallbacks() throws InvalidObjectException { try { while (list != null) { AccessController.doPrivileged( new PrivilegedExceptionAction() { public Void run() throws InvalidObjectException { list.obj.validateObject(); return null; } }, list.acc); list = list.next; } } catch (PrivilegedActionException ex) { list = null; throw (InvalidObjectException) ex.getException(); } } /** * Resets the callback list to its initial (empty) state. */ public void clear() { list = null; } } class StepObjectInputStream extends MarshallerObjectInputStream { private final ClonerPutField clonerPutField; private final Object clone; private final SerializableClass cloneInfo; /** validation callback list */ private ValidationList vlist; StepObjectInputStream(final Queue steps, final ClonerPutField clonerPutField, final Object clone, final SerializableClass cloneInfo) throws IOException { super(new StepObjectInput(steps)); this.clonerPutField = clonerPutField; this.clone = clone; this.cloneInfo = cloneInfo; this.vlist = null; } public void defaultReadObject() throws IOException, ClassNotFoundException { storeFields(cloneInfo, clone, clonerPutField); } public GetField readFields() throws IOException, ClassNotFoundException { return new GetField() { public ObjectStreamClass getObjectStreamClass() { throw new UnsupportedOperationException(); } public boolean defaulted(final String name) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted(); } public boolean get(final String name, final boolean val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getBoolean(); } public byte get(final String name, final byte val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getByte(); } public char get(final String name, final char val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getChar(); } public short get(final String name, final short val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getShort(); } public int get(final String name, final int val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getInt(); } public long get(final String name, final long val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getLong(); } public float get(final String name, final float val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getFloat(); } public double get(final String name, final double val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getDouble(); } public Object get(final String name, final Object val) throws IOException { final ReadField field = clonerPutField.fieldMap.get(name); return field == null || field.isDefaulted() ? val : field.getObject(); } }; } public void registerValidation(final ObjectInputValidation obj, final int priority) throws NotActiveException, InvalidObjectException { if ( vlist == null ) { this.vlist = new ValidationList(); } // System.out.println("Register Validation Cb for " + obj.toString()); vlist.register(obj, priority); } // do to the unusual use of this class, allow calling code to execute the callbacks, if any. public void doCallbacks() { if ( vlist != null ) { // System.out.println("Doing Validation Cbs"); try { vlist.doCallbacks(); } catch (java.io.InvalidObjectException e) { // System.out.println("\t Validation exceptioned out: " + e); } } } } class ClonerPutField extends ObjectOutputStream.PutField { private final Map fieldDefMap = new HashMap(); private final Map fieldMap = new HashMap(); private SerializableField getField(final String name, final Kind kind) { final SerializableField field = fieldDefMap.get(name); if (field == null) { throw new IllegalArgumentException("No field named '" + name + "' could be found"); } if (field.getKind() != kind) { throw new IllegalArgumentException("Field '" + name + "' is the wrong type (expected " + kind + ", got " + field.getKind() + ")"); } return field; } private void defineFields(final SerializableClass clazz) { for (SerializableField field : clazz.getFields()) { fieldDefMap.put(field.getName(), field); } } public void put(final String name, final boolean val) { fieldMap.put(name, new BooleanReadField(getField(name, Kind.BOOLEAN), val)); } public void put(final String name, final byte val) { fieldMap.put(name, new ByteReadField(getField(name, Kind.BYTE), val)); } public void put(final String name, final char val) { fieldMap.put(name, new CharReadField(getField(name, Kind.CHAR), val)); } public void put(final String name, final short val) { fieldMap.put(name, new ShortReadField(getField(name, Kind.SHORT), val)); } public void put(final String name, final int val) { fieldMap.put(name, new IntReadField(getField(name, Kind.INT), val)); } public void put(final String name, final long val) { fieldMap.put(name, new LongReadField(getField(name, Kind.LONG), val)); } public void put(final String name, final float val) { fieldMap.put(name, new FloatReadField(getField(name, Kind.FLOAT), val)); } public void put(final String name, final double val) { fieldMap.put(name, new DoubleReadField(getField(name, Kind.DOUBLE), val)); } public void put(final String name, final Object val) { fieldMap.put(name, new ObjectReadField(getField(name, Kind.OBJECT), val)); } @Deprecated public void write(final ObjectOutput out) throws IOException { throw new UnsupportedOperationException(); } } class StepObjectInput extends AbstractObjectInput implements Unmarshaller { private final Queue steps; private Step current; private int idx; StepObjectInput(final Queue steps) throws IOException { super(bufferSize); this.steps = steps; current = steps.poll(); super.start(new ByteInput() { public int read() throws IOException { while (current != null) { if (current instanceof ByteDataStep) { final ByteDataStep step = (ByteDataStep) current; final byte[] bytes = step.getBytes(); if (idx == bytes.length) { current = steps.poll(); idx = 0; } else { final byte b = bytes[idx++]; return b & 0xff; } } else { // an object is pending return -1; } } return -1; } public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } public int read(final byte[] b, int off, int len) throws IOException { if (len == 0) return 0; int t = 0; while (current != null && len > 0) { if (current instanceof ByteDataStep) { final ByteDataStep step = (ByteDataStep) current; final byte[] bytes = step.getBytes(); final int blen = bytes.length; if (idx == blen) { current = steps.poll(); idx = 0; } else { final int c = Math.min(blen - idx, len); System.arraycopy(bytes, idx, b, off, c); idx += c; off += c; len -= c; t += c; if (idx == blen) { current = steps.poll(); idx = 0; } } } else { // an object is pending return t == 0 ? -1 : t; } } return t == 0 ? -1 : t; } public int available() throws IOException { return current instanceof ByteDataStep ? ((ByteDataStep) current).getBytes().length - idx : 0; } public long skip(long n) throws IOException { long t = 0; while (current != null && n > 0) { if (current instanceof ByteDataStep) { final ByteDataStep step = (ByteDataStep) current; final byte[] bytes = step.getBytes(); final int blen = bytes.length; if (idx == blen) { current = steps.poll(); idx = 0; } else { final int c = (int) Math.min((long) blen - idx, n); idx += c; n -= c; if (idx == blen) { current = steps.poll(); idx = 0; } } } else { // an object is pending return t; } } return t; } public void close() throws IOException { current = null; } }); } protected Object doReadObject(final boolean unshared) throws ClassNotFoundException, IOException { Step step = current; while (step instanceof ByteDataStep) { step = steps.poll(); } if (step == null) { current = null; throw new EOFException(); } current = steps.poll(); // not really true, just IDEA being silly again @SuppressWarnings("UnnecessaryThis") final Object clone = SerializingCloner.this.clone(((CloneStep) step).getOrig()); return clone; } public void finish() throws IOException { throw new UnsupportedOperationException(); } public void start(final ByteInput byteInput) throws IOException { throw new UnsupportedOperationException(); } public void clearInstanceCache() throws IOException { throw new UnsupportedOperationException(); } public void clearClassCache() throws IOException { throw new UnsupportedOperationException(); } } private static final class ByteDataStep extends Step { private final byte[] bytes; private ByteDataStep(final byte[] bytes) { this.bytes = bytes; } byte[] getBytes() { return bytes; } } private static final class CloneStep extends Step { private final Object orig; private CloneStep(final Object orig) { this.orig = orig; } Object getOrig() { return orig; } } }