All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.marshalling.cloner.SerializingCloner Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * 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.
 */

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.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 (cloneInfo.isRecord()) {
            // follow record ctor protocol
            SerializableField[] fields = cloneInfo.getFields();
            Object[] args = new Object[fields.length];
            if (info.isRecord()) {
                for (SerializableField field : info.getFields()) {
                    SerializableField cloneField = cloneInfo.getSerializableFieldByName(field.getName());
                    if (cloneField != null) {
                        args[cloneField.getRecordComponentIndex()] = clone(field.getRecordComponentValue(orig));
                    }
                }
            } else {
                // not a record, just make a best effort
                for (SerializableField field : info.getFields()) {
                    SerializableField cloneField = cloneInfo.getSerializableFieldByName(field.getName());
                    if (cloneField != null) {
                        switch (field.getKind()) {
                            case BOOLEAN: {
                                args[cloneField.getRecordComponentIndex()] = Boolean.valueOf(field.getBoolean(orig));
                                break;
                            }
                            case BYTE: {
                                args[cloneField.getRecordComponentIndex()] = Byte.valueOf(field.getByte(orig));
                                break;
                            }
                            case CHAR: {
                                args[cloneField.getRecordComponentIndex()] = Character.valueOf(field.getChar(orig));
                                break;
                            }
                            case DOUBLE: {
                                args[cloneField.getRecordComponentIndex()] = Double.valueOf(field.getDouble(orig));
                                break;
                            }
                            case FLOAT: {
                                args[cloneField.getRecordComponentIndex()] = Float.valueOf(field.getFloat(orig));
                                break;
                            }
                            case INT: {
                                args[cloneField.getRecordComponentIndex()] = Integer.valueOf(field.getInt(orig));
                                break;
                            }
                            case LONG: {
                                args[cloneField.getRecordComponentIndex()] = Long.valueOf(field.getLong(orig));
                                break;
                            }
                            case SHORT: {
                                args[cloneField.getRecordComponentIndex()] = Short.valueOf(field.getShort(orig));
                                break;
                            }
                            case OBJECT: {
                                args[cloneField.getRecordComponentIndex()] = field.getObject(orig);
                                break;
                            }
                        }
                    }
                }
            }
            clone = cloneInfo.invokeRecordCanonicalConstructor(args);
            clones.put(orig, clone);
        } else 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()) {
                cloneInfo.callReadObject(clone, createStepObjectInputStream(clone, cloneInfo, fields, steps));
            } else {
                storeFields(cloneInfo, clone, fields);
            }
        } else {
            prepareFields(orig, fields);
            cloneFields(fields);
            if (cloneInfo.hasReadObject()) {
                cloneInfo.callReadObject(clone, createStepObjectInputStream(clone, cloneInfo, fields, new ArrayDeque()));
            } 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();
        }
    }

    class StepObjectInputStream extends MarshallerObjectInputStream {

        private final ClonerPutField clonerPutField;
        private final Object clone;
        private final SerializableClass cloneInfo;

        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;
        }

        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 {
        }
    }

    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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy