Re: NotSerializable object issue...
On Jul 4, 10:41 pm, Slash <java.ko...@gmail.com> wrote:
Hello all...
I have got a ghost bug running around my project* for a while now, and
I haven't been able to track it... to put it simple, I am writing a
serializable object to disk (an object of class Game) when the player
saves the game. It works most of the times, however, under
circumstances I haven't been able to figure out, it fails with a
NotSerializable Exception over an object that is supposed never to be
Serialized. (GFXUserInterface, which I dont want to just implement
Serializable, as I would then have to make its members Serializable,
and it would be an ugly hack)
I have looked for member variables of this type and its
superclasses... all of them are transient. I did the same for all the
interfaces implemented by this type... no luck...
Can you recomend me a program that recursively checks a serializable
class to see if it has non transient non serializable fields? I tried
FindBugs, but it didn't report anything about it...
Thanks in advance for any help that could be provided.
--
Slashhttp://www.santiagoz.com
* CastlevaniaRL, a roguelike game (http://www.santiagoz.com/web)
I found a great class on this page:
http://herebebeasties.com/2007-02-08/javaionotserializableexception-in-your-httpsession/
(full source code here:
http://svn.apache.org/viewvc/incubator/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/util/io/SerializableChecker.java?revision=532837&view=markup).
It gives you a stack showing the path from the main object being
serialized to the unserializable object.
I've modified it to remove Wicket-specific stuff, and made a few minor
improvements, too. Here's my version:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version
2.0
* (the "License"); you may not use this file except in compliance
with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.Externalizable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
/**
* Utility class that analyzes objects for non-serializable nodes.
Construct
* with the object you want to check, and then call {@link
#writeObject(Object)}. When a
* non-serializable object is found, a {@link
NotSerializableException} is
* thrown with a message that shows the trace up to the not-
serializable object.
* The exception is thrown for the first non-serializable instance it
* encounters, so multiple problems will not be shown.
* <p>
* As this class depends heavily on JDK's serialization internals
using
* introspection, analyzing may not be possible, for instance when the
runtime
* environment does not have sufficient rights to set fields
accessible that
* would otherwise be hidden. You should call
* {@link SerializableChecker#isAvailable()} to see whether this class
can
* operate properly. If it doesn't, you should fall back to e.g. re-
throwing/
* printing the {@link NotSerializableException} you probably got
before using
* this class.
* </p>
*
* <p>
* Modified by Adam Crume to remove all Wicket-specific logic and add
various minor improvements.
* </p>
*
* @author eelcohillenius
* @author Al Maw
* @author Adam Crume
*/
public class SerializableChecker extends ObjectOutputStream {
private static final NoopOutputStream DUMMY_OUTPUT_STREAM = new
NoopOutputStream();
/** Whether we can execute the tests. If false, check will just
return. */
private static boolean available = true;
// this hack - accessing the serialization API through introspection
- is
// the only way to use Java serialization for our purposes without
writing
// the whole thing from scratch (and even then, it would be limited).
This
// way of working is of course fragile for internal API changes, but
as we
// do an extra check on availability and we report when we can't use
this
// introspection fu, we'll find out soon enough and clients on this
class
// can fall back on Java's default exception for serialization errors
(which
// sucks and is the main reason for this attempt).
private static final Method lookup;
private static final Method getClassDataLayoutMethod;
private static final Method getNumObjFields;
private static final Method getObjFieldValues;
private static final Method fieldMethod;
static {
try {
lookup = ObjectStreamClass.class.getDeclaredMethod("lookup", new
Class[] {Class.class, Boolean.TYPE});
lookup.setAccessible(true);
getClassDataLayoutMethod =
ObjectStreamClass.class.getDeclaredMethod("getClassDataLayout", null);
getClassDataLayoutMethod.setAccessible(true);
getNumObjFields =
ObjectStreamClass.class.getDeclaredMethod("getNumObjFields", null);
getNumObjFields.setAccessible(true);
getObjFieldValues =
ObjectStreamClass.class.getDeclaredMethod("getObjFieldValues", new
Class[] {Object.class, Object[].class});
getObjFieldValues.setAccessible(true);
fieldMethod = ObjectStreamField.class.getDeclaredMethod("getField",
null);
fieldMethod.setAccessible(true);
} catch(SecurityException e) {
available = false;
throw new RuntimeException(e);
} catch(NoSuchMethodException e) {
available = false;
throw new RuntimeException(e);
}
}
/**
* Gets whether we can execute the tests. If false, calling {@link
#check(Object)}
* will just return and you are advised to rely on the
* {@link NotSerializableException}. Clients are advised to call this
* method prior to calling the check method.
*
* @return whether security settings and underlying API etc allow for
* accessing the serialization API using introspection
*/
public static boolean isAvailable() {
return available;
}
/** object stack that with the trace path. */
private final LinkedList traceStack = new LinkedList();
/** set for checking circular references. */
private final HandleTable checked = new HandleTable(10, (float)3.00);
/** root object being analyzed. */
private Object root;
/** cache for classes - writeObject methods. */
private Map writeObjectMethodCache = new HashMap();
/** current full field description. */
private StringBuffer fieldDescription;
public SerializableChecker() throws IOException {
}
/**
* @see java.io.ObjectOutputStream#reset()
*/
public void reset() throws IOException {
root = null;
checked.clear();
fieldDescription = null;
traceStack.clear();
writeObjectMethodCache.clear();
}
private void check(final Object obj) throws IOException {
if(obj == null) {
return;
}
Class cls = obj.getClass();
traceStack.add(new TraceSlot(obj, fieldDescription));
if(!(obj instanceof Serializable)) {
throw new NotSerializableException(toPrettyPrintedStack(cls));
}
final ObjectStreamClass desc;
try {
desc = (ObjectStreamClass)lookup.invoke(null, new Object[] {cls,
Boolean.TRUE});
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}
if(cls.isPrimitive()) {
// skip
} else if(cls.isArray()) {
checked.assign(obj);
Class ccl = cls.getComponentType();
if(!(ccl.isPrimitive())) {
Object[] objs = (Object[])obj;
for(int i = 0; i < objs.length; i++) {
fieldDescription.append('[').append(i).append(']');
check(objs[i]);
}
}
} else if(obj instanceof Externalizable && !Proxy.isProxyClass(cls))
{
Externalizable extObj = (Externalizable)obj;
extObj.writeExternal(new ObjectOutputAdaptor() {
private int count = 0;
public void writeObject(Object streamObj) throws IOException {
// Check for circular reference.
if(checked.contains(streamObj)) {
return;
}
checked.assign(streamObj);
fieldDescription.append("[write:").append(count++).append(']');
check(streamObj);
}
});
} else {
Method writeObjectMethod = null;
Object o = writeObjectMethodCache.get(cls);
if(o != null) {
if(o instanceof Method) {
writeObjectMethod = (Method)o;
}
} else {
try {
writeObjectMethod = cls.getDeclaredMethod("writeObject", new
Class[] {java.io.ObjectOutputStream.class});
} catch(SecurityException e) {
// we can't access/ set accessible to true
writeObjectMethodCache.put(cls, Boolean.FALSE);
} catch(NoSuchMethodException e) {
// cls doesn't have that method
writeObjectMethodCache.put(cls, Boolean.FALSE);
}
}
if(writeObjectMethod != null) {
class InterceptingObjectOutputStream extends ObjectOutputStream {
private int counter;
InterceptingObjectOutputStream() throws IOException {
super(DUMMY_OUTPUT_STREAM);
enableReplaceObject(true);
}
protected Object replaceObject(Object streamObj) throws
IOException {
if(streamObj == obj) {
return streamObj;
}
counter++;
// Check for circular reference.
if(checked.contains(streamObj)) {
return null;
}
checked.assign(obj);
fieldDescription.append("[write:").append(counter).append(']');
check(streamObj);
return streamObj;
}
}
InterceptingObjectOutputStream ioos = new
InterceptingObjectOutputStream();
ioos.writeObject(obj);
} else {
Object[] slots;
try {
slots = (Object[])getClassDataLayoutMethod.invoke(desc, null);
} catch(Exception e) {
throw new RuntimeException(e);
}
for(int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc;
try {
Field descField = slots[i].getClass().getDeclaredField("desc");
descField.setAccessible(true);
slotDesc = (ObjectStreamClass)descField.get(slots[i]);
} catch(Exception e) {
throw new RuntimeException(e);
}
checked.assign(obj);
checkFields(obj, slotDesc);
}
}
}
traceStack.removeLast();
}
private void checkFields(Object obj, ObjectStreamClass desc) throws
IOException {
int numFields;
try {
numFields = ((Integer)getNumObjFields.invoke(desc,
null)).intValue();
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}
if(numFields > 0) {
ObjectStreamField[] fields = desc.getFields();
Object[] objVals = new Object[numFields];
int numPrimFields = fields.length - objVals.length;
try {
getObjFieldValues.invoke(desc, new Object[] {obj, objVals});
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}
for(int i = 0; i < objVals.length; i++) {
Object val = objVals[i];
if(val instanceof String || val instanceof Boolean || val
instanceof Class) {
// filter out common cases
continue;
}
// Check for circular reference.
if(checked.contains(val)) {
continue;
}
ObjectStreamField fieldDesc = fields[numPrimFields + i];
Field field;
try {
field = (Field)fieldMethod.invoke(fieldDesc, null);
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}
fieldDescription = new StringBuffer(field.toString());
check(val);
}
}
}
/**
* Dump with identation.
*
* @param type
* the type that couldn't be serialized
* @return A very pretty dump
*/
private final String toPrettyPrintedStack(Class type) {
StringBuffer result = new StringBuffer();
StringBuffer spaces = new StringBuffer();
result.append("Unable to serialize class: ");
result.append(type.getName());
result.append("\nField hierarchy is:");
for(Iterator i = traceStack.listIterator(); i.hasNext();) {
spaces.append(" ");
TraceSlot slot = (TraceSlot)i.next();
result.append('\n').append(spaces).append(slot.fieldDescription);
result.append(" [class=").append(slot.object.getClass().getName());
result.append(']');
}
result.append(" <----- field that is not serializable");
return result.toString();
}
/**
* @see
java.io.ObjectOutputStream#writeObjectOverride(java.lang.Object)
*/
protected final void writeObjectOverride(Object obj) throws
IOException {
if(!available) {
return;
}
root = obj;
check(root);
}
public void flush() throws IOException {
}
public void close() throws IOException {
}
/**
* Lightweight identity hash table which maps objects to integer
handles,
* assigned in ascending order (comes from {@link
ObjectOutputStream}).
*/
private static class HandleTable {
/** number of mappings in table/next available handle */
private int size;
/** size threshold determining when to expand hash spine */
private int threshold;
/** factor for computing size threshold */
private final float loadFactor;
/** maps hash value -> candidate handle value */
private int[] spine;
/** maps handle value -> next candidate handle value */
private int[] next;
/** maps handle value -> associated object */
private Object[] objs;
/**
* Construct.
*/
public HandleTable() {
this(16, 0.75f);
}
/**
* Construct.
*
* @param initialCapacity
* @param loadFactor
*/
public HandleTable(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor;
spine = new int[initialCapacity];
next = new int[initialCapacity];
objs = new Object[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
clear();
}
/**
* Assigns next available handle to given object, and returns handle
value.
* Handles are assigned in ascending order starting at 0.
*
* @param obj
* @return
*/
public int assign(Object obj) {
if(size >= next.length) {
growEntries();
}
if(size >= threshold) {
growSpine();
}
insert(obj, size);
return size++;
}
/**
* Clears this table.
*/
public void clear() {
Arrays.fill(spine, -1);
Arrays.fill(objs, 0, size, null);
size = 0;
}
/**
* Whether this table contains the provided object.
*
* @param obj
* object to check
* @return whether it contains the provided object
*/
public boolean contains(Object obj) {
return lookup(obj) != -1;
}
/**
* Looks up and returns handle associated with given object, or -1
if no
* mapping found.
*
* @param obj
* @return
*/
public int lookup(Object obj) {
if(size == 0) {
return -1;
}
int index = hash(obj) % spine.length;
for(int i = spine[index]; i >= 0; i = next[i]) {
if(objs[i] == obj) {
return i;
}
}
return -1;
}
/**
* @return The number of elements
*/
public int size() {
return size;
}
private void growEntries() {
int newLength = (next.length << 1) + 1;
int[] newNext = new int[newLength];
System.arraycopy(next, 0, newNext, 0, size);
next = newNext;
Object[] newObjs = new Object[newLength];
System.arraycopy(objs, 0, newObjs, 0, size);
objs = newObjs;
}
private void growSpine() {
spine = new int[(spine.length << 1) + 1];
threshold = (int)(spine.length * loadFactor);
Arrays.fill(spine, -1);
for(int i = 0; i < size; i++) {
insert(objs[i], i);
}
}
private int hash(Object obj) {
return System.identityHashCode(obj) & 0x7FFFFFFF;
}
private void insert(Object obj, int handle) {
int index = hash(obj) % spine.length;
objs[handle] = obj;
next[handle] = spine[index];
spine[index] = handle;
}
}
/**
* Does absolutely nothing.
*/
private static class NoopOutputStream extends OutputStream {
public void close() {
}
public void flush() {
}
public void write(byte[] b) {
}
public void write(byte[] b, int i, int l) {
}
public void write(int b) {
}
}
private static abstract class ObjectOutputAdaptor implements
ObjectOutput {
public void close() throws IOException {
}
public void flush() throws IOException {
}
public void write(byte[] b) throws IOException {
}
public void write(byte[] b, int off, int len) throws IOException {
}
public void write(int b) throws IOException {
}
public void writeBoolean(boolean v) throws IOException {
}
public void writeByte(int v) throws IOException {
}
public void writeBytes(String s) throws IOException {
}
public void writeChar(int v) throws IOException {
}
public void writeChars(String s) throws IOException {
}
public void writeDouble(double v) throws IOException {
}
public void writeFloat(float v) throws IOException {
}
public void writeInt(int v) throws IOException {
}
public void writeLong(long v) throws IOException {
}
public void writeShort(int v) throws IOException {
}
public void writeUTF(String str) throws IOException {
}
}
/** Holds information about the field and the resulting object being
traced. */
private static final class TraceSlot {
private final String fieldDescription;
private final Object object;
TraceSlot(Object object, StringBuffer fieldDescription) {
this.object = object;
this.fieldDescription = fieldDescription==null?
null:fieldDescription.toString();
}
public String toString() {
return object.getClass() + " - " + fieldDescription;
}
}
}