001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.lang3;
019
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.InvalidObjectException;
025import java.io.ObjectInputStream;
026import java.io.ObjectOutputStream;
027import java.io.ObjectStreamClass;
028import java.io.OutputStream;
029import java.io.Serializable;
030import java.util.Objects;
031
032/**
033 * Performs additional functionality for serialization.
034 *
035 * <ul>
036 * <li>Deep clone using serialization</li>
037 * <li>Serialize managing finally and IOException</li>
038 * <li>Deserialize managing finally and IOException</li>
039 * </ul>
040 *
041 * <p>
042 * This class throws exceptions for invalid {@code null} inputs. Each method documents its behavior in more detail.
043 * </p>
044 * <p>
045 * If you want to secure deserialization with a whitelist or blacklist, please use Apache Commons IO's
046 * {@link org.apache.commons.io.serialization.ValidatingObjectInputStream ValidatingObjectInputStream}.
047 * </p>
048 * <p>
049 * #ThreadSafe#
050 * </p>
051 *
052 * @see org.apache.commons.io.serialization.ValidatingObjectInputStream
053 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
054 * @since 1.0
055 */
056public class SerializationUtils {
057
058    /**
059     * Custom specialization of the standard JDK {@link ObjectInputStream} that uses a custom {@link ClassLoader} to resolve a class. If the specified
060     * {@link ClassLoader} is not able to resolve the class, the context classloader of the current thread will be used. This way, the standard deserialization
061     * also works in web application containers and application servers, regardless of which {@link ClassLoader} loaded the class that encapsulates
062     * serialization/deserialization.
063     *
064     * <p>
065     * For more in-depth information about the problem for which this class here is a workaround, see the JIRA issue LANG-626.
066     * </p>
067     */
068    static final class ClassLoaderAwareObjectInputStream extends ObjectInputStream {
069
070        private final ClassLoader classLoader;
071
072        /**
073         * Constructs a new instance.
074         *
075         * @param in          The {@link InputStream}.
076         * @param classLoader classloader to use
077         * @throws IOException Thrown if an I/O error occurs while reading stream header.
078         * @see java.io.ObjectInputStream
079         */
080        ClassLoaderAwareObjectInputStream(final InputStream in, final ClassLoader classLoader) throws IOException {
081            super(in);
082            this.classLoader = classLoader;
083        }
084
085        /**
086         * Overridden version that uses the parameterized {@link ClassLoader} or the {@link ClassLoader} of the current {@link Thread} to resolve the class.
087         *
088         * @param desc An instance of class {@link ObjectStreamClass}.
089         * @return A {@link Class} object corresponding to {@code desc}.
090         * @throws IOException Thrown if an I/O error occurs.
091         * @throws ClassNotFoundException Thrown if class of a serialized object cannot be found.
092         */
093        @Override
094        protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
095            final String name = desc.getName();
096            try {
097                return Class.forName(name, false, classLoader);
098            } catch (final ClassNotFoundException ex) {
099                try {
100                    return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
101                } catch (final ClassNotFoundException cnfe) {
102                    final Class<?> cls = ClassUtils.getPrimitiveClass(name);
103                    if (cls != null) {
104                        return cls;
105                    }
106                    throw cnfe;
107                }
108            }
109        }
110    }
111
112    /**
113     * Deep clones an {@link Object} using serialization.
114     *
115     * <p>
116     * This is many times slower than writing clone methods by hand on all objects in your object graph. However, for complex object graphs, or for those that
117     * don't support deep cloning this can be a simple alternative implementation. Of course all the objects must be {@link Serializable}.
118     * </p>
119     *
120     * @param <T>    the type of the object involved.
121     * @param object The {@link Serializable} object to clone.
122     * @return The cloned object.
123     * @throws SerializationException Thrown if the serialization fails.
124     * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
125     */
126    public static <T extends Serializable> T clone(final T object) {
127        if (object == null) {
128            return null;
129        }
130        final ByteArrayInputStream bais = new ByteArrayInputStream(serialize(object));
131        final Class<T> cls = ObjectUtils.getClass(object);
132        try (ClassLoaderAwareObjectInputStream in = new ClassLoaderAwareObjectInputStream(bais, cls.getClassLoader())) {
133            // When we serialize and deserialize an object, it is reasonable to assume the deserialized object is of the
134            // same type as the original serialized object
135            return (T) in.readObject();
136        } catch (final ClassNotFoundException | IOException ex) {
137            throw new SerializationException(String.format("%s while reading cloned object data", ex.getClass().getSimpleName()), ex);
138        }
139    }
140
141    /**
142     * Deserializes a single {@link Object} from an array of bytes.
143     *
144     * <p>
145     * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. Without Generics in this declaration, the
146     * call site must type cast and can cause the same ClassCastException. Note that in both cases, the ClassCastException is in the call site, not in this
147     * method.
148     * </p>
149     * <p>
150     * If you want to secure deserialization with a whitelist or blacklist, please use Apache Commons IO's
151     * {@link org.apache.commons.io.serialization.ValidatingObjectInputStream ValidatingObjectInputStream}.
152     * </p>
153     *
154     * @param <T>        the object type to be deserialized.
155     * @param objectData The serialized object, must not be null.
156     * @return The deserialized object.
157     * @throws NullPointerException   Thrown if {@code objectData} is {@code null}.
158     * @throws SerializationException Thrown if the serialization fails.
159     * @see org.apache.commons.io.serialization.ValidatingObjectInputStream
160     * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
161     */
162    public static <T> T deserialize(final byte[] objectData) {
163        Objects.requireNonNull(objectData, "objectData");
164        return deserialize(new ByteArrayInputStream(objectData));
165    }
166
167    /**
168     * Deserializes an {@link Object} from the specified stream.
169     *
170     * <p>
171     * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also exception handling, in the application
172     * code.
173     * </p>
174     *
175     * <p>
176     * The stream passed in is not buffered internally within this method. This is the responsibility of your application if desired.
177     * </p>
178     *
179     * <p>
180     * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. Without Generics in this declaration, the
181     * call site must type cast and can cause the same ClassCastException. Note that in both cases, the ClassCastException is in the call site, not in this
182     * method.
183     * </p>
184     *
185     * <p>
186     * If you want to secure deserialization with a whitelist or blacklist, please use Apache Commons IO's
187     * {@link org.apache.commons.io.serialization.ValidatingObjectInputStream ValidatingObjectInputStream}.
188     * </p>
189     *
190     * @param <T>         the object type to be deserialized.
191     * @param inputStream The serialized object input stream, must not be null.
192     * @return The deserialized object.
193     * @throws NullPointerException   Thrown if {@code inputStream} is {@code null}.
194     * @throws SerializationException Thrown if the serialization fails.
195     * @see org.apache.commons.io.serialization.ValidatingObjectInputStream
196     * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
197     */
198    @SuppressWarnings("resource") // inputStream is managed by the caller
199    public static <T> T deserialize(final InputStream inputStream) {
200        Objects.requireNonNull(inputStream, "inputStream");
201        try (ObjectInputStream in = new ObjectInputStream(inputStream)) {
202            @SuppressWarnings("unchecked")
203            final T obj = (T) in.readObject();
204            return obj;
205        } catch (final ClassNotFoundException | IOException | NegativeArraySizeException ex) {
206            throw new SerializationException(ex);
207        }
208    }
209
210    /**
211     * Checks that the specified object reference is not {@code null} and throws a customized {@link InvalidObjectException} if it is. This method is designed
212     * primarily for doing state validation in {@link Serializable} class's {@code readObject(ObjectInputStream)} methods.
213     *
214     * @param obj     The object reference to check for nullity.
215     * @param message detail message to be used in the event that a {@link InvalidObjectException} is thrown.
216     * @param <T>     the type of the reference.
217     * @return {@code obj} if not {@code null}.
218     * @throws InvalidObjectException Thrown if {@code obj} is {@code null}.
219     * @see Serializable
220     * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
221     * @since 3.21.0
222     */
223    public static <T> T requireNonNull(final T obj, final String message) throws InvalidObjectException {
224        if (obj == null) {
225            throw new InvalidObjectException(message);
226        }
227        return obj;
228    }
229
230    /**
231     * Performs a serialization roundtrip. Serializes and deserializes the given object, great for testing objects that implement {@link Serializable}.
232     *
233     * @param <T> The type of the object involved.
234     * @param obj The object to roundtrip.
235     * @return The serialized and deserialized object.
236     * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
237     * @since 3.3
238     */
239    @SuppressWarnings("unchecked") // OK, because we serialized a type `T`
240    public static <T extends Serializable> T roundtrip(final T obj) {
241        return (T) deserialize(serialize(obj));
242    }
243
244    /**
245     * Serializes an {@link Object} to a byte array for storage/serialization.
246     *
247     * @param obj The object to serialize to bytes.
248     * @return A byte[] with the converted Serializable.
249     * @throws SerializationException Thrown if the serialization fails.
250     * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
251     */
252    public static byte[] serialize(final Serializable obj) {
253        final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
254        serialize(obj, baos);
255        return baos.toByteArray();
256    }
257
258    /**
259     * Serializes an {@link Object} to the specified stream.
260     *
261     * <p>
262     * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also exception handling, in the application
263     * code.
264     * </p>
265     *
266     * <p>
267     * The stream passed in is not buffered internally within this method. This is the responsibility of your application if desired.
268     * </p>
269     *
270     * @param obj          The object to serialize to bytes, may be null.
271     * @param outputStream The stream to write to, must not be null.
272     * @throws NullPointerException   Thrown if {@code outputStream} is {@code null}.
273     * @throws SerializationException Thrown if the serialization fails.
274     * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
275     */
276    @SuppressWarnings("resource") // outputStream is managed by the caller
277    public static void serialize(final Serializable obj, final OutputStream outputStream) {
278        Objects.requireNonNull(outputStream, "outputStream");
279        try (ObjectOutputStream out = new ObjectOutputStream(outputStream)) {
280            out.writeObject(obj);
281        } catch (final IOException ex) {
282            throw new SerializationException(ex);
283        }
284    }
285
286    /**
287     * SerializationUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
288     * {@code SerializationUtils.clone(object)}.
289     *
290     * <p>
291     * This constructor is public to permit tools that require a JavaBean instance to operate.
292     * </p>
293     *
294     * @since 2.0
295     * @deprecated TODO Make private in 4.0.
296     */
297    @Deprecated
298    public SerializationUtils() {
299        // empty
300    }
301}