1/**
2 * Copyright 2006-2017 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.objenesis.instantiator.basic;
17
18import java.io.ByteArrayOutputStream;
19import java.io.DataOutputStream;
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.NotSerializableException;
23import java.io.ObjectInputStream;
24import java.io.ObjectStreamClass;
25import java.io.ObjectStreamConstants;
26import java.io.Serializable;
27
28import org.objenesis.ObjenesisException;
29import org.objenesis.instantiator.ObjectInstantiator;
30import org.objenesis.instantiator.annotations.Instantiator;
31import org.objenesis.instantiator.annotations.Typology;
32
33/**
34 * Instantiates a class by using a dummy input stream that always feeds data for an empty object of
35 * the same kind. NOTE: This instantiator may not work properly if the class being instantiated
36 * defines a "readResolve" method, since it may return objects that have been returned previously
37 * (i.e., there's no guarantee that the returned object is a new one), or even objects from a
38 * completely different class.
39 *
40 * @author Leonardo Mesquita
41 * @see org.objenesis.instantiator.ObjectInstantiator
42 */
43@Instantiator(Typology.SERIALIZATION)
44public class ObjectInputStreamInstantiator<T> implements ObjectInstantiator<T> {
45   private static class MockStream extends InputStream {
46
47      private int pointer;
48      private byte[] data;
49      private int sequence;
50      private static final int[] NEXT = new int[] {1, 2, 2};
51      private byte[][] buffers;
52
53      private final byte[] FIRST_DATA;
54      private static byte[] HEADER;
55      private static byte[] REPEATING_DATA;
56
57      static {
58         initialize();
59      }
60
61      private static void initialize() {
62         try {
63            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
64            DataOutputStream dout = new DataOutputStream(byteOut);
65            dout.writeShort(ObjectStreamConstants.STREAM_MAGIC);
66            dout.writeShort(ObjectStreamConstants.STREAM_VERSION);
67            HEADER = byteOut.toByteArray();
68
69            byteOut = new ByteArrayOutputStream();
70            dout = new DataOutputStream(byteOut);
71
72            dout.writeByte(ObjectStreamConstants.TC_OBJECT);
73            dout.writeByte(ObjectStreamConstants.TC_REFERENCE);
74            dout.writeInt(ObjectStreamConstants.baseWireHandle);
75            REPEATING_DATA = byteOut.toByteArray();
76         }
77         catch(IOException e) {
78            throw new Error("IOException: " + e.getMessage());
79         }
80
81      }
82
83      public MockStream(Class<?> clazz) {
84         this.pointer = 0;
85         this.sequence = 0;
86         this.data = HEADER;
87
88         // (byte) TC_OBJECT
89         // (byte) TC_CLASSDESC
90         // (short length)
91         // (byte * className.length)
92         // (long)serialVersionUID
93         // (byte) SC_SERIALIZABLE
94         // (short)0 <fields>
95         // TC_ENDBLOCKDATA
96         // TC_NULL
97         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
98         DataOutputStream dout = new DataOutputStream(byteOut);
99         try {
100            dout.writeByte(ObjectStreamConstants.TC_OBJECT);
101            dout.writeByte(ObjectStreamConstants.TC_CLASSDESC);
102            dout.writeUTF(clazz.getName());
103            dout.writeLong(ObjectStreamClass.lookup(clazz).getSerialVersionUID());
104            dout.writeByte(ObjectStreamConstants.SC_SERIALIZABLE);
105            dout.writeShort((short) 0); // Zero fields
106            dout.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA);
107            dout.writeByte(ObjectStreamConstants.TC_NULL);
108         }
109         catch(IOException e) {
110            throw new Error("IOException: " + e.getMessage());
111         }
112         this.FIRST_DATA = byteOut.toByteArray();
113         buffers = new byte[][] {HEADER, FIRST_DATA, REPEATING_DATA};
114      }
115
116      private void advanceBuffer() {
117         pointer = 0;
118         sequence = NEXT[sequence];
119         data = buffers[sequence];
120      }
121
122      @Override
123      public int read() throws IOException {
124         int result = data[pointer++];
125         if(pointer >= data.length) {
126            advanceBuffer();
127         }
128
129         return result;
130      }
131
132      @Override
133      public int available() throws IOException {
134         return Integer.MAX_VALUE;
135      }
136
137      @Override
138      public int read(byte[] b, int off, int len) throws IOException {
139         int left = len;
140         int remaining = data.length - pointer;
141
142         while(remaining <= left) {
143            System.arraycopy(data, pointer, b, off, remaining);
144            off += remaining;
145            left -= remaining;
146            advanceBuffer();
147            remaining = data.length - pointer;
148         }
149         if(left > 0) {
150            System.arraycopy(data, pointer, b, off, left);
151            pointer += left;
152         }
153
154         return len;
155      }
156   }
157
158   private ObjectInputStream inputStream;
159
160   public ObjectInputStreamInstantiator(Class<T> clazz) {
161      if(Serializable.class.isAssignableFrom(clazz)) {
162         try {
163            this.inputStream = new ObjectInputStream(new MockStream(clazz));
164         }
165         catch(IOException e) {
166            throw new Error("IOException: " + e.getMessage());
167         }
168      }
169      else {
170         throw new ObjenesisException(new NotSerializableException(clazz + " not serializable"));
171      }
172   }
173
174   @SuppressWarnings("unchecked")
175   public T newInstance() {
176      try {
177         return (T) inputStream.readObject();
178      }
179      catch(ClassNotFoundException e) {
180         throw new Error("ClassNotFoundException: " + e.getMessage());
181      }
182      catch(Exception e) {
183         throw new ObjenesisException(e);
184      }
185   }
186}
187