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 *     http://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
18package org.apache.harmony.tests.java.io;
19
20import dalvik.system.DexFile;
21import dalvik.system.VMRuntime;
22import java.io.Externalizable;
23import java.io.File;
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.ObjectInput;
27import java.io.ObjectOutput;
28import java.io.ObjectStreamClass;
29import java.io.ObjectStreamField;
30import java.io.Serializable;
31import java.lang.reflect.Field;
32import java.lang.reflect.InvocationTargetException;
33import java.lang.reflect.Method;
34import java.lang.reflect.Proxy;
35import java.nio.file.Files;
36import java.nio.file.StandardCopyOption;
37import junit.framework.TestCase;
38
39public class ObjectStreamClassTest extends TestCase {
40
41    static class DummyClass implements Serializable {
42        private static final long serialVersionUID = 999999999999999L;
43
44        long bam = 999L;
45
46        int ham = 9999;
47
48        public static long getUID() {
49            return serialVersionUID;
50        }
51    }
52
53    /**
54     * java.io.ObjectStreamClass#forClass()
55     */
56    public void test_forClass() {
57        // Need to test during serialization to be sure an instance is
58        // returned
59        ObjectStreamClass osc = ObjectStreamClass.lookup(DummyClass.class);
60        assertEquals("forClass returned an object: " + osc.forClass(),
61                DummyClass.class, osc.forClass());
62    }
63
64    /**
65     * java.io.ObjectStreamClass#getField(java.lang.String)
66     */
67    public void test_getFieldLjava_lang_String() {
68        ObjectStreamClass osc = ObjectStreamClass.lookup(DummyClass.class);
69        assertEquals("getField did not return correct field", 'J', osc
70                .getField("bam").getTypeCode());
71        assertNull("getField did not null for non-existent field", osc
72                .getField("wham"));
73    }
74
75    /**
76     * java.io.ObjectStreamClass#getFields()
77     */
78    public void test_getFields() {
79        ObjectStreamClass osc = ObjectStreamClass.lookup(DummyClass.class);
80        ObjectStreamField[] osfArray = osc.getFields();
81        assertTrue(
82                "Array of fields should be of length 2 but is instead of length: "
83                        + osfArray.length, osfArray.length == 2);
84    }
85
86    /**
87     * java.io.ObjectStreamClass#getName()
88     */
89    public void test_getName() {
90        ObjectStreamClass osc = ObjectStreamClass.lookup(DummyClass.class);
91        assertEquals(
92                "getName returned incorrect name: " + osc.getName(),
93                "org.apache.harmony.tests.java.io.ObjectStreamClassTest$DummyClass",
94                osc.getName());
95    }
96
97    /**
98     * java.io.ObjectStreamClass#getSerialVersionUID()
99     */
100    public void test_getSerialVersionUID() {
101        ObjectStreamClass osc = ObjectStreamClass.lookup(DummyClass.class);
102        assertTrue("getSerialversionUID returned incorrect uid: "
103                + osc.getSerialVersionUID() + " instead of "
104                + DummyClass.getUID(), osc.getSerialVersionUID() == DummyClass
105                .getUID());
106    }
107
108    static class SyntheticTest implements Serializable {
109        private int i;
110
111        private class X implements Serializable {
112            public int get() {
113                return i;
114            }
115        }
116
117        public X foo() {
118            return new X();
119        }
120    }
121
122    /**
123     * java.io.ObjectStreamClass#lookup(java.lang.Class)
124     */
125    public void test_lookupLjava_lang_Class() {
126        ObjectStreamClass osc = ObjectStreamClass.lookup(DummyClass.class);
127        assertEquals(
128                "lookup returned wrong class: " + osc.getName(),
129                "org.apache.harmony.tests.java.io.ObjectStreamClassTest$DummyClass",
130                osc.getName());
131    }
132
133    /**
134     * java.io.ObjectStreamClass#toString()
135     */
136    public void test_toString() {
137        ObjectStreamClass osc = ObjectStreamClass.lookup(DummyClass.class);
138        String oscString = osc.toString();
139
140        // The previous test was more specific than the spec so it was replaced
141        // with the test below
142        assertTrue("toString returned incorrect string: " + osc.toString(),
143                oscString.indexOf("serialVersionUID") >= 0
144                        && oscString.indexOf("999999999999999L") >= 0);
145    }
146
147    public void testSerialization() {
148        ObjectStreamClass osc = ObjectStreamClass
149                .lookup(ObjectStreamClass.class);
150        assertEquals(0, osc.getFields().length);
151    }
152
153    public void test_specialTypes() {
154        Class<?> proxyClass = Proxy.getProxyClass(this.getClass()
155                .getClassLoader(), new Class[] { Runnable.class });
156
157        ObjectStreamClass proxyStreamClass = ObjectStreamClass
158                .lookup(proxyClass);
159
160        assertEquals("Proxy classes should have zero serialVersionUID", 0,
161                proxyStreamClass.getSerialVersionUID());
162        ObjectStreamField[] proxyFields = proxyStreamClass.getFields();
163        assertEquals("Proxy classes should have no serialized fields", 0,
164                proxyFields.length);
165
166        ObjectStreamClass enumStreamClass = ObjectStreamClass
167                .lookup(Thread.State.class);
168
169        assertEquals("Enum classes should have zero serialVersionUID", 0,
170                enumStreamClass.getSerialVersionUID());
171        ObjectStreamField[] enumFields = enumStreamClass.getFields();
172        assertEquals("Enum classes should have no serialized fields", 0,
173                enumFields.length);
174    }
175
176    /**
177     * @since 1.6
178     */
179    static class NonSerialzableClass {
180        private static final long serialVersionUID = 1l;
181
182        public static long getUID() {
183            return serialVersionUID;
184        }
185    }
186
187    /**
188     * @since 1.6
189     */
190    static class ExternalizableClass implements Externalizable {
191
192        private static final long serialVersionUID = -4285635779249689129L;
193
194        public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {
195            throw new ClassNotFoundException();
196        }
197
198        public void writeExternal(ObjectOutput output) throws IOException {
199            throw new IOException();
200        }
201
202    }
203
204    /**
205     * java.io.ObjectStreamClass#lookupAny(java.lang.Class)
206     * @since 1.6
207     */
208    public void test_lookupAnyLjava_lang_Class() {
209        // Test for method java.io.ObjectStreamClass
210        // java.io.ObjectStreamClass.lookupAny(java.lang.Class)
211        ObjectStreamClass osc = ObjectStreamClass.lookupAny(DummyClass.class);
212        assertEquals("lookup returned wrong class: " + osc.getName(),
213                "org.apache.harmony.tests.java.io.ObjectStreamClassTest$DummyClass", osc
214                .getName());
215
216        osc = ObjectStreamClass.lookupAny(NonSerialzableClass.class);
217        assertEquals("lookup returned wrong class: " + osc.getName(),
218                "org.apache.harmony.tests.java.io.ObjectStreamClassTest$NonSerialzableClass",
219                osc.getName());
220
221        osc = ObjectStreamClass.lookupAny(ExternalizableClass.class);
222        assertEquals("lookup returned wrong class: " + osc.getName(),
223                "org.apache.harmony.tests.java.io.ObjectStreamClassTest$ExternalizableClass",
224                osc.getName());
225
226        osc = ObjectStreamClass.lookup(NonSerialzableClass.class);
227        assertNull(osc);
228    }
229
230    // http://b/28106822
231    public void testBug28106822() throws Exception {
232        int savedTargetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion();
233        try {
234            // Assert behavior up to 24
235            VMRuntime.getRuntime().setTargetSdkVersion(24);
236            Method getConstructorId = ObjectStreamClass.class.getDeclaredMethod(
237                    "getConstructorId", Class.class);
238            getConstructorId.setAccessible(true);
239
240            assertEquals(1189998819991197253L, getConstructorId.invoke(null, Object.class));
241            assertEquals(1189998819991197253L, getConstructorId.invoke(null, String.class));
242
243            Method newInstance = ObjectStreamClass.class.getDeclaredMethod("newInstance",
244                    Class.class, Long.TYPE);
245            newInstance.setAccessible(true);
246
247            Object obj = newInstance.invoke(null, String.class, 0 /* ignored */);
248            assertNotNull(obj);
249            assertTrue(obj instanceof String);
250
251            // Assert behavior from API 25
252            VMRuntime.getRuntime().setTargetSdkVersion(25);
253            try {
254                getConstructorId.invoke(null, Object.class);
255                fail();
256            } catch (InvocationTargetException expected) {
257                assertTrue(expected.getCause() instanceof UnsupportedOperationException);
258            }
259            try {
260                newInstance.invoke(null, String.class, 0 /* ignored */);
261                fail();
262            } catch (InvocationTargetException expected) {
263                assertTrue(expected.getCause() instanceof UnsupportedOperationException);
264            }
265
266        } finally {
267            VMRuntime.getRuntime().setTargetSdkVersion(savedTargetSdkVersion);
268        }
269    }
270
271    // Class without <clinit> method
272    public static class NoClinitParent {
273    }
274    // Class without <clinit> method
275    public static class NoClinitChildWithNoClinitParent extends NoClinitParent {
276    }
277
278    // Class with <clinit> method
279    public static class ClinitParent {
280        // This field will trigger creation of <clinit> method for this class
281        private static final String TAG = ClinitParent.class.getName();
282        static {
283
284        }
285    }
286    // Class without <clinit> but with parent that has <clinit> method
287    public static class NoClinitChildWithClinitParent extends ClinitParent {
288    }
289
290    // http://b/29064453
291    public void testHasClinit() throws Exception {
292        Method hasStaticInitializer =
293            ObjectStreamClass.class.getDeclaredMethod("hasStaticInitializer", Class.class,
294                                                      boolean.class);
295        hasStaticInitializer.setAccessible(true);
296
297        assertTrue((Boolean)
298                   hasStaticInitializer.invoke(null, ClinitParent.class,
299                                               false /* checkSuperclass */));
300
301        // RI will return correctly False in this case, but android has been returning true
302        // in this particular case. We're returning true to enable deserializing classes
303        // like NoClinitChildWithClinitParent without explicit serialVersionID field.
304        assertTrue((Boolean)
305                   hasStaticInitializer.invoke(null, NoClinitChildWithClinitParent.class,
306                                               false /* checkSuperclass */));
307        assertFalse((Boolean)
308                    hasStaticInitializer.invoke(null, NoClinitParent.class,
309                                                false /* checkSuperclass */));
310        assertFalse((Boolean)
311                    hasStaticInitializer.invoke(null, NoClinitChildWithNoClinitParent.class,
312                                                false /* checkSuperclass */));
313
314
315        assertTrue((Boolean)
316                   hasStaticInitializer.invoke(null, ClinitParent.class,
317                                               true /* checkSuperclass */));
318        assertFalse((Boolean)
319                   hasStaticInitializer.invoke(null, NoClinitChildWithClinitParent.class,
320                                               true /* checkSuperclass */));
321        assertFalse((Boolean)
322                    hasStaticInitializer.invoke(null, NoClinitParent.class,
323                                                true /* checkSuperclass */));
324        assertFalse((Boolean)
325                    hasStaticInitializer.invoke(null, NoClinitChildWithNoClinitParent.class,
326                                                true /* checkSuperclass */));
327    }
328
329    // http://b/29721023
330    public void testClassWithSameFieldName() throws Exception {
331        // Load class from dex, it's not possible to create a class with same-named
332        // fields in java (but it's allowed in dex).
333        File sameFieldNames = File.createTempFile("sameFieldNames", ".dex");
334        InputStream dexIs = this.getClass().getClassLoader().
335            getResourceAsStream("tests/api/java/io/sameFieldNames.dex");
336        assertNotNull(dexIs);
337
338        try {
339            Files.copy(dexIs, sameFieldNames.toPath(), StandardCopyOption.REPLACE_EXISTING);
340            DexFile dexFile = new DexFile(sameFieldNames);
341            Class<?> clazz = dexFile.loadClass("sameFieldNames", getClass().getClassLoader());
342            ObjectStreamClass osc = ObjectStreamClass.lookup(clazz);
343            assertEquals(4, osc.getFields().length);
344            dexFile.close();
345        } finally {
346            if (sameFieldNames.exists()) {
347                sameFieldNames.delete();
348            }
349        }
350    }
351}
352