View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3;
18  
19  import static org.apache.commons.lang3.LangAssertions.assertNullPointerException;
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNotSame;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertSame;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  import static org.junit.jupiter.api.Assertions.fail;
31  
32  import java.io.ByteArrayInputStream;
33  import java.io.ByteArrayOutputStream;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.ObjectInputStream;
37  import java.io.ObjectOutputStream;
38  import java.io.OutputStream;
39  import java.io.Serializable;
40  import java.lang.reflect.Constructor;
41  import java.lang.reflect.Modifier;
42  import java.util.HashMap;
43  import java.util.function.Supplier;
44  
45  import org.junit.jupiter.api.BeforeEach;
46  import org.junit.jupiter.api.Test;
47  
48  final class ClassNotFoundSerialization implements Serializable {
49  
50      private static final long serialVersionUID = 1L;
51  
52      private void readObject(final ObjectInputStream in) throws ClassNotFoundException    {
53          throw new ClassNotFoundException(SerializationUtilsTest.CLASS_NOT_FOUND_MESSAGE);
54      }
55  }
56  
57  interface SerializableSupplier<T> extends Supplier<T>, Serializable {
58      // empty
59  }
60  
61  /**
62   * Tests {@link SerializationUtils}.
63   */
64  public class SerializationUtilsTest extends AbstractLangTest {
65  
66      static final String CLASS_NOT_FOUND_MESSAGE = "ClassNotFoundSerialization.readObject fake exception";
67  
68      protected static final String SERIALIZE_IO_EXCEPTION_MESSAGE = "Anonymous OutputStream I/O exception";
69  
70      public static byte[] intToBytes(final int v) {
71          return new byte[] { (byte) (v >>> 24), (byte) (v >>> 16), (byte) (v >>> 8), (byte) v };
72      }
73  
74      public static byte[] replaceLastInt(final byte[] src, final int from, final int to) {
75          final byte[] fromB = intToBytes(from);
76          final byte[] toB = intToBytes(to);
77          final byte[] out = src.clone();
78          for (int i = out.length - 4; i >= 0; i--) {
79              if (out[i] == fromB[0] && out[i + 1] == fromB[1] && out[i + 2] == fromB[2] && out[i + 3] == fromB[3]) {
80                  out[i] = toB[0];
81                  out[i + 1] = toB[1];
82                  out[i + 2] = toB[2];
83                  out[i + 3] = toB[3];
84                  return out;
85              }
86          }
87          fail("No legitimate int in stream, serialization must keep hashCode in default field set");
88          return null;
89      }
90  
91      private String iString;
92  
93      private Integer iInteger;
94  
95      private HashMap<Object, Object> iMap;
96  
97      @BeforeEach
98      public void setUp() {
99          iString = "foo";
100         iInteger = Integer.valueOf(7);
101         iMap = new HashMap<>();
102         iMap.put("FOO", iString);
103         iMap.put("BAR", iInteger);
104     }
105 
106     @Test
107     void testClone() {
108         final Object test = SerializationUtils.clone(iMap);
109         assertNotNull(test);
110         assertInstanceOf(HashMap.class, test);
111         assertNotSame(test, iMap);
112         final HashMap<?, ?> testMap = (HashMap<?, ?>) test;
113         assertEquals(iString, testMap.get("FOO"));
114         assertNotSame(iString, testMap.get("FOO"));
115         assertEquals(iInteger, testMap.get("BAR"));
116         assertNotSame(iInteger, testMap.get("BAR"));
117         assertEquals(iMap, testMap);
118     }
119 
120     @Test
121     void testCloneNull() {
122         final Object test = SerializationUtils.clone(null);
123         assertNull(test);
124     }
125 
126     @Test
127     void testCloneSerializableSupplier() {
128         final SerializableSupplier<String> supplier = () -> "test";
129         assertEquals("test", supplier.get());
130         final SerializableSupplier<String> clone = SerializationUtils.clone(supplier);
131         assertEquals("test", clone.get());
132     }
133 
134     @Test
135     void testCloneUnserializable() {
136         iMap.put(new Object(), new Object());
137         assertThrows(SerializationException.class, () -> SerializationUtils.clone(iMap));
138     }
139 
140     @SuppressWarnings("deprecation")
141     @Test
142     void testConstructor() {
143         assertNotNull(new SerializationUtils());
144         final Constructor<?>[] cons = SerializationUtils.class.getDeclaredConstructors();
145         assertEquals(1, cons.length);
146         assertTrue(Modifier.isPublic(cons[0].getModifiers()));
147         assertTrue(Modifier.isPublic(SerializationUtils.class.getModifiers()));
148         assertFalse(Modifier.isFinal(SerializationUtils.class.getModifiers()));
149     }
150 
151     @Test
152     void testDeserializeBytes() throws Exception {
153         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
154         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
155             oos.writeObject(iMap);
156             oos.flush();
157         }
158 
159         final Object test = SerializationUtils.deserialize(streamReal.toByteArray());
160         assertNotNull(test);
161         assertInstanceOf(HashMap.class, test);
162         assertNotSame(test, iMap);
163         final HashMap<?, ?> testMap = (HashMap<?, ?>) test;
164         assertEquals(iString, testMap.get("FOO"));
165         assertNotSame(iString, testMap.get("FOO"));
166         assertEquals(iInteger, testMap.get("BAR"));
167         assertNotSame(iInteger, testMap.get("BAR"));
168         assertEquals(iMap, testMap);
169     }
170 
171     @Test
172     void testDeserializeBytesBadStream() {
173         assertThrows(SerializationException.class, () -> SerializationUtils.deserialize(new byte[0]));
174     }
175 
176     @Test
177     void testDeserializeBytesNull() {
178         assertNullPointerException(() -> SerializationUtils.deserialize((byte[]) null));
179     }
180 
181     @Test
182     void testDeserializeBytesOfNull() throws Exception {
183         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
184         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
185             oos.writeObject(null);
186             oos.flush();
187         }
188 
189         final Object test = SerializationUtils.deserialize(streamReal.toByteArray());
190         assertNull(test);
191     }
192 
193     @Test
194     void testDeserializeClassCastException() {
195         final String value = "Hello";
196         final byte[] serialized = SerializationUtils.serialize(value);
197         assertEquals(value, SerializationUtils.deserialize(serialized));
198         assertThrows(ClassCastException.class, () -> {
199             // Causes ClassCastException in call site, not in SerializationUtils.deserialize
200             @SuppressWarnings("unused") // needed to cause Exception
201             final Integer i = SerializationUtils.deserialize(serialized);
202         });
203     }
204 
205     @Test
206     void testDeserializeStream() throws Exception {
207         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
208         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
209             oos.writeObject(iMap);
210             oos.flush();
211         }
212 
213         final ByteArrayInputStream inTest = new ByteArrayInputStream(streamReal.toByteArray());
214         final Object test = SerializationUtils.deserialize(inTest);
215         assertNotNull(test);
216         assertInstanceOf(HashMap.class, test);
217         assertNotSame(test, iMap);
218         final HashMap<?, ?> testMap = (HashMap<?, ?>) test;
219         assertEquals(iString, testMap.get("FOO"));
220         assertNotSame(iString, testMap.get("FOO"));
221         assertEquals(iInteger, testMap.get("BAR"));
222         assertNotSame(iInteger, testMap.get("BAR"));
223         assertEquals(iMap, testMap);
224     }
225 
226     @Test
227     void testDeserializeStreamBadStream() {
228         assertThrows(SerializationException.class, () -> SerializationUtils.deserialize(new ByteArrayInputStream(new byte[0])));
229     }
230 
231     @Test
232     void testDeserializeStreamClassNotFound() throws Exception {
233         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
234         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
235             oos.writeObject(new ClassNotFoundSerialization());
236             oos.flush();
237         }
238 
239         final ByteArrayInputStream inTest = new ByteArrayInputStream(streamReal.toByteArray());
240         final SerializationException se = assertThrows(SerializationException.class, () -> SerializationUtils.deserialize(inTest));
241         assertEquals("java.lang.ClassNotFoundException: " + CLASS_NOT_FOUND_MESSAGE, se.getMessage());
242     }
243 
244     @Test
245     void testDeserializeStreamNull() {
246         assertNullPointerException(() -> SerializationUtils.deserialize((InputStream) null));
247     }
248 
249     @Test
250     void testDeserializeStreamOfNull() throws Exception {
251         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
252         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
253             oos.writeObject(null);
254             oos.flush();
255         }
256 
257         final ByteArrayInputStream inTest = new ByteArrayInputStream(streamReal.toByteArray());
258         final Object test = SerializationUtils.deserialize(inTest);
259         assertNull(test);
260     }
261 
262     @Test
263     void testException() {
264         SerializationException serEx;
265         final Exception ex = new Exception();
266 
267         serEx = new SerializationException();
268         assertSame(null, serEx.getMessage());
269         assertSame(null, serEx.getCause());
270 
271         serEx = new SerializationException("Message");
272         assertSame("Message", serEx.getMessage());
273         assertSame(null, serEx.getCause());
274 
275         serEx = new SerializationException(ex);
276         assertEquals("java.lang.Exception", serEx.getMessage());
277         assertSame(ex, serEx.getCause());
278 
279         serEx = new SerializationException("Message", ex);
280         assertSame("Message", serEx.getMessage());
281         assertSame(ex, serEx.getCause());
282     }
283 
284     @Test
285     void testNegativeByteArray() {
286         final byte[] byteArray = {
287             (byte) -84, (byte) -19, (byte) 0, (byte) 5, (byte) 125, (byte) -19, (byte) 0,
288             (byte) 5, (byte) 115, (byte) 114, (byte) -1, (byte) 97, (byte) 122, (byte) -48, (byte) -65
289         };
290 
291         assertThrows(SerializationException.class, () -> SerializationUtils.deserialize(new ByteArrayInputStream(byteArray)));
292     }
293 
294     @Test
295     void testPrimitiveTypeClassSerialization() {
296         final Class<?>[] primitiveTypes = { byte.class, short.class, int.class, long.class, float.class, double.class,
297                 boolean.class, char.class, void.class };
298 
299         for (final Class<?> primitiveType : primitiveTypes) {
300             final Class<?> clone = SerializationUtils.clone(primitiveType);
301             assertEquals(primitiveType, clone);
302         }
303     }
304 
305     @Test
306     void testRoundtrip() {
307         final HashMap<Object, Object> newMap = SerializationUtils.roundtrip(iMap);
308         assertEquals(iMap, newMap);
309     }
310 
311     @Test
312     void testSerializeBytes() throws Exception {
313         final byte[] testBytes = SerializationUtils.serialize(iMap);
314 
315         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
316         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
317             oos.writeObject(iMap);
318             oos.flush();
319         }
320 
321         final byte[] realBytes = streamReal.toByteArray();
322         assertEquals(testBytes.length, realBytes.length);
323         assertArrayEquals(realBytes, testBytes);
324     }
325 
326     @Test
327     void testSerializeBytesNull() throws Exception {
328         final byte[] testBytes = SerializationUtils.serialize(null);
329 
330         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
331         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
332             oos.writeObject(null);
333             oos.flush();
334         }
335 
336         final byte[] realBytes = streamReal.toByteArray();
337         assertEquals(testBytes.length, realBytes.length);
338         assertArrayEquals(realBytes, testBytes);
339     }
340 
341     @Test
342     void testSerializeBytesUnserializable() {
343         iMap.put(new Object(), new Object());
344         assertThrows(SerializationException.class, () -> SerializationUtils.serialize(iMap));
345     }
346 
347     @Test
348     void testSerializeIOException() {
349         // forces an IOException when the ObjectOutputStream is created, to test not closing the stream
350         // in the finally block
351         final OutputStream streamTest = new OutputStream() {
352             @Override
353             public void write(final int arg0) throws IOException {
354                 throw new IOException(SERIALIZE_IO_EXCEPTION_MESSAGE);
355             }
356         };
357         final SerializationException e =
358                 assertThrows(SerializationException.class, () -> SerializationUtils.serialize(iMap, streamTest));
359         assertEquals("java.io.IOException: " + SERIALIZE_IO_EXCEPTION_MESSAGE, e.getMessage());
360     }
361 
362     @Test
363     void testSerializeStream() throws Exception {
364         final ByteArrayOutputStream streamTest = new ByteArrayOutputStream();
365         SerializationUtils.serialize(iMap, streamTest);
366 
367         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
368         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
369             oos.writeObject(iMap);
370             oos.flush();
371         }
372 
373         final byte[] testBytes = streamTest.toByteArray();
374         final byte[] realBytes = streamReal.toByteArray();
375         assertEquals(testBytes.length, realBytes.length);
376         assertArrayEquals(realBytes, testBytes);
377     }
378 
379     @Test
380     void testSerializeStreamNullNull() {
381         assertNullPointerException(() -> SerializationUtils.serialize(null, null));
382     }
383 
384     @Test
385     void testSerializeStreamNullObj() throws Exception {
386         final ByteArrayOutputStream streamTest = new ByteArrayOutputStream();
387         SerializationUtils.serialize(null, streamTest);
388 
389         final ByteArrayOutputStream streamReal = new ByteArrayOutputStream();
390         try (ObjectOutputStream oos = new ObjectOutputStream(streamReal)) {
391             oos.writeObject(null);
392             oos.flush();
393         }
394 
395         final byte[] testBytes = streamTest.toByteArray();
396         final byte[] realBytes = streamReal.toByteArray();
397         assertEquals(testBytes.length, realBytes.length);
398         assertArrayEquals(realBytes, testBytes);
399     }
400 
401     @Test
402     void testSerializeStreamObjNull() {
403         assertNullPointerException(() -> SerializationUtils.serialize(iMap, null));
404     }
405 
406     @Test
407     void testSerializeStreamUnserializable() {
408         final ByteArrayOutputStream streamTest = new ByteArrayOutputStream();
409         iMap.put(new Object(), new Object());
410         assertThrows(SerializationException.class, () -> SerializationUtils.serialize(iMap, streamTest));
411     }
412 }