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  package org.apache.commons.collections;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.ObjectInputStream;
27  import java.io.ObjectOutputStream;
28  import java.io.OutputStream;
29  import java.io.Serializable;
30  
31  /**
32   * Abstract test class for {@link java.lang.Object} methods and contracts.
33   * <p>
34   * To use, simply extend this class, and implement
35   * the {@link #makeObject()} method.
36   * <p>
37   * If your {@link Object} fails one of these tests by design,
38   * you may still use this base set of cases.  Simply override the
39   * test case (method) your {@link Object} fails.
40   *
41   * @version $Revision: 646780 $ $Date: 2008-04-10 13:48:07 +0100 (Thu, 10 Apr 2008) $
42   * 
43   * @author Rodney Waldhoff
44   * @author Stephen Colebourne
45   * @author Anonymous
46   */
47  public abstract class AbstractTestObject extends BulkTest {
48  
49      /** Current major release for Collections */
50      public static final int COLLECTIONS_MAJOR_VERSION = 3;
51      
52      /**
53       * JUnit constructor.
54       * 
55       * @param testName  the test class name
56       */
57      public AbstractTestObject(String testName) {
58          super(testName);
59      }
60  
61      //-----------------------------------------------------------------------
62      /**
63       * Implement this method to return the object to test.
64       * 
65       * @return the object to test
66       */
67      public abstract Object makeObject();
68  
69      /**
70       * Override this method if a subclass is testing an object
71       * that cannot serialize an "empty" Collection.
72       * (e.g. Comparators have no contents)
73       * 
74       * @return true
75       */
76      public boolean supportsEmptyCollections() {
77          return true;
78      }
79  
80      /**
81       * Override this method if a subclass is testing an object
82       * that cannot serialize a "full" Collection.
83       * (e.g. Comparators have no contents)
84       * 
85       * @return true
86       */
87      public boolean supportsFullCollections() {
88          return true;
89      }
90  
91      /**
92       * Is serialization testing supported.
93       * Default is true.
94       */
95      public boolean isTestSerialization() {
96          return true;
97      }
98  
99      /**
100      * Returns true to indicate that the collection supports equals() comparisons.
101      * This implementation returns true;
102      */
103     public boolean isEqualsCheckable() {
104         return true;
105     }
106 
107     //-----------------------------------------------------------------------
108     public void testObjectEqualsSelf() {
109         Object obj = makeObject();
110         assertEquals("A Object should equal itself", obj, obj);
111     }
112 
113     public void testEqualsNull() {
114         Object obj = makeObject();
115         assertEquals(false, obj.equals(null)); // make sure this doesn't throw NPE either
116     }
117 
118     public void testObjectHashCodeEqualsSelfHashCode() {
119         Object obj = makeObject();
120         assertEquals("hashCode should be repeatable", obj.hashCode(), obj.hashCode());
121     }
122 
123     public void testObjectHashCodeEqualsContract() {
124         Object obj1 = makeObject();
125         if (obj1.equals(obj1)) {
126             assertEquals(
127                 "[1] When two objects are equal, their hashCodes should be also.",
128                 obj1.hashCode(), obj1.hashCode());
129         }
130         Object obj2 = makeObject();
131         if (obj1.equals(obj2)) {
132             assertEquals(
133                 "[2] When two objects are equal, their hashCodes should be also.",
134                 obj1.hashCode(), obj2.hashCode());
135             assertTrue(
136                 "When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true",
137                 obj2.equals(obj1));
138         }
139     }
140 
141     public void testSerializeDeserializeThenCompare() throws Exception {
142         Object obj = makeObject();
143         if (obj instanceof Serializable && isTestSerialization()) {
144             ByteArrayOutputStream buffer = new ByteArrayOutputStream();
145             ObjectOutputStream out = new ObjectOutputStream(buffer);
146             out.writeObject(obj);
147             out.close();
148 
149             ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()));
150             Object dest = in.readObject();
151             in.close();
152             if (isEqualsCheckable()) {
153                 assertEquals("obj != deserialize(serialize(obj))", obj, dest);
154             }
155         }
156     }
157 
158     /**
159      * Sanity check method, makes sure that any Serializable
160      * class can be serialized and de-serialized in memory, 
161      * using the handy makeObject() method
162      * 
163      * @throws IOException
164      * @throws ClassNotFoundException
165      */
166     public void testSimpleSerialization() throws Exception {
167         Object o = makeObject();
168         if (o instanceof Serializable && isTestSerialization()) {
169             byte[] objekt = writeExternalFormToBytes((Serializable) o);
170             Object p = readExternalFormFromBytes(objekt);
171         }
172     }
173 
174     /**
175      * Tests serialization by comparing against a previously stored version in CVS.
176      * If the test object is serializable, confirm that a canonical form exists.
177      */
178     public void testCanonicalEmptyCollectionExists() {
179         if (supportsEmptyCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) {
180             Object object = makeObject();
181             if (object instanceof Serializable) {
182                 String name = getCanonicalEmptyCollectionName(object);
183                 assertTrue(
184                     "Canonical empty collection (" + name + ") is not in CVS",
185                     new File(name).exists());
186             }
187         }
188     }
189 
190     /**
191      * Tests serialization by comparing against a previously stored version in CVS.
192      * If the test object is serializable, confirm that a canonical form exists.
193      */
194     public void testCanonicalFullCollectionExists() {
195         if (supportsFullCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) {
196             Object object = makeObject();
197             if (object instanceof Serializable) {
198                 String name = getCanonicalFullCollectionName(object);
199                 assertTrue(
200                     "Canonical full collection (" + name + ") is not in CVS",
201                     new File(name).exists());
202             }
203         }
204     }
205 
206     // protected implementation
207     //-----------------------------------------------------------------------
208     /**
209      * Get the version of Collections that this object tries to
210      * maintain serialization compatibility with. Defaults to 1, the
211      * earliest Collections version. (Note: some collections did not
212      * even exist in this version).
213      * 
214      * This constant makes it possible for TestMap (and other subclasses,
215      * if necessary) to automatically check CVS for a versionX copy of a
216      * Serialized object, so we can make sure that compatibility is maintained.
217      * See, for example, TestMap.getCanonicalFullMapName(Map map).
218      * Subclasses can override this variable, indicating compatibility
219      * with earlier Collections versions.
220      * 
221      * @return The version, or <code>null</code> if this object shouldn't be
222      * tested for compatibility with previous versions.
223      */
224     public String getCompatibilityVersion() {
225         return "1";
226     }
227 
228     protected String getCanonicalEmptyCollectionName(Object object) {
229         StringBuffer retval = new StringBuffer();
230         retval.append("data/test/");
231         String colName = object.getClass().getName();
232         colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length());
233         retval.append(colName);
234         retval.append(".emptyCollection.version");
235         retval.append(getCompatibilityVersion());
236         retval.append(".obj");
237         return retval.toString();
238     }
239 
240     protected String getCanonicalFullCollectionName(Object object) {
241         StringBuffer retval = new StringBuffer();
242         retval.append("data/test/");
243         String colName = object.getClass().getName();
244         colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length());
245         retval.append(colName);
246         retval.append(".fullCollection.version");
247         retval.append(getCompatibilityVersion());
248         retval.append(".obj");
249         return retval.toString();
250     }
251 
252     /**
253      * Write a Serializable or Externalizable object as
254      * a file at the given path.  NOT USEFUL as part
255      * of a unit test; this is just a utility method
256      * for creating disk-based objects in CVS that can become
257      * the basis for compatibility tests using
258      * readExternalFormFromDisk(String path)
259      * 
260      * @param o Object to serialize
261      * @param path path to write the serialized Object
262      * @exception IOException
263      */
264     protected void writeExternalFormToDisk(Serializable o, String path) throws IOException {
265         FileOutputStream fileStream = new FileOutputStream(path);
266         writeExternalFormToStream(o, fileStream);
267     }
268 
269     /**
270      * Converts a Serializable or Externalizable object to
271      * bytes.  Useful for in-memory tests of serialization
272      * 
273      * @param o Object to convert to bytes
274      * @return serialized form of the Object
275      * @exception IOException
276      */
277     protected byte[] writeExternalFormToBytes(Serializable o) throws IOException {
278         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
279         writeExternalFormToStream(o, byteStream);
280         return byteStream.toByteArray();
281     }
282 
283     /**
284      * Reads a Serialized or Externalized Object from disk.
285      * Useful for creating compatibility tests between
286      * different CVS versions of the same class
287      * 
288      * @param path path to the serialized Object
289      * @return the Object at the given path
290      * @exception IOException
291      * @exception ClassNotFoundException
292      */
293     protected Object readExternalFormFromDisk(String path) throws IOException, ClassNotFoundException {
294         FileInputStream stream = new FileInputStream(path);
295         return readExternalFormFromStream(stream);
296     }
297 
298     /**
299      * Read a Serialized or Externalized Object from bytes.
300      * Useful for verifying serialization in memory.
301      * 
302      * @param b byte array containing a serialized Object
303      * @return Object contained in the bytes
304      * @exception IOException
305      * @exception ClassNotFoundException
306      */
307     protected Object readExternalFormFromBytes(byte[] b) throws IOException, ClassNotFoundException {
308         ByteArrayInputStream stream = new ByteArrayInputStream(b);
309         return readExternalFormFromStream(stream);
310     }
311 
312     protected boolean skipSerializedCanonicalTests() {
313         return Boolean.getBoolean("org.apache.commons.collections:with-clover");
314     }
315 
316     // private implementation
317     //-----------------------------------------------------------------------
318     private Object readExternalFormFromStream(InputStream stream) throws IOException, ClassNotFoundException {
319         ObjectInputStream oStream = new ObjectInputStream(stream);
320         return oStream.readObject();
321     }
322 
323     private void writeExternalFormToStream(Serializable o, OutputStream stream) throws IOException {
324         ObjectOutputStream oStream = new ObjectOutputStream(stream);
325         oStream.writeObject(o);
326     }
327 
328 }