/*
 * Copyright (C) 2004, 2005 Joe Walnes.
 * Copyright (C) 2006, 2007 XStream Committers.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 * 
 * Created on 23. August 2004 by Joe Walnes
 */
package com.thoughtworks.xstream.converters.reflection;

import com.thoughtworks.xstream.converters.ConversionException;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Convenience wrapper to invoke special serialization methods on objects (and perform reflection caching).
 *
 * @author Joe Walnes
 */
public class SerializationMethodInvoker {

    private Map cache = Collections.synchronizedMap(new HashMap());
    private static final Object NO_METHOD = new Object();
    private static final Object[] EMPTY_ARGS = new Object[0];

    /**
     * Resolves an object as native serialization does by calling readResolve(), if available.
     */
    public Object callReadResolve(Object result) {
        if (result == null) {
            return null;
        } else {
            Method readResolveMethod = getMethod(result.getClass(), "readResolve", null, true);
            if (readResolveMethod != null) {
                try {
                    return readResolveMethod.invoke(result, EMPTY_ARGS);
                } catch (IllegalAccessException e) {
                    throw new ObjectAccessException("Could not call " + result.getClass().getName() + ".readResolve()", e);
                } catch (InvocationTargetException e) {
                    throw new ObjectAccessException("Could not call " + result.getClass().getName() + ".readResolve()", e.getTargetException());
                }
            } else {
                return result;
            }
        }
    }

    public Object callWriteReplace(Object object) {
        if (object == null) {
            return null;
        } else {
            Method writeReplaceMethod = getMethod(object.getClass(), "writeReplace", null, true);
            if (writeReplaceMethod != null) {
                try {
                    Object[] EMPTY_ARGS = new Object[0];
                    return writeReplaceMethod.invoke(object, EMPTY_ARGS);
                } catch (IllegalAccessException e) {
                    throw new ObjectAccessException("Could not call " + object.getClass().getName() + ".writeReplace()", e);
                } catch (InvocationTargetException e) {
                    throw new ObjectAccessException("Could not call " + object.getClass().getName() + ".writeReplace()", e.getTargetException());
                }
            } else {
                return object;
            }
        }
    }

    public boolean supportsReadObject(Class type, boolean includeBaseClasses) {
        return getMethod(type, "readObject", new Class[]{ObjectInputStream.class}, includeBaseClasses) != null;
    }

    public void callReadObject(Class type, Object object, ObjectInputStream stream) {
        try {
            Method readObjectMethod = getMethod(type, "readObject", new Class[]{ObjectInputStream.class}, false);
            readObjectMethod.invoke(object, new Object[]{stream});
        } catch (IllegalAccessException e) {
            throw new ConversionException("Could not call " + object.getClass().getName() + ".readObject()", e);
        } catch (InvocationTargetException e) {
            throw new ConversionException("Could not call " + object.getClass().getName() + ".readObject()", e.getTargetException());
        }
    }

    public boolean supportsWriteObject(Class type, boolean includeBaseClasses) {
        return getMethod(type, "writeObject", new Class[]{ObjectOutputStream.class}, includeBaseClasses) != null;
    }

    public void callWriteObject(Class type, Object instance, ObjectOutputStream stream) {
        try {
            Method readObjectMethod = getMethod(type, "writeObject", new Class[]{ObjectOutputStream.class}, false);
            readObjectMethod.invoke(instance, new Object[]{stream});
        } catch (IllegalAccessException e) {
            throw new ConversionException("Could not call " + instance.getClass().getName() + ".writeObject()", e);
        } catch (InvocationTargetException e) {
            throw new ConversionException("Could not call " + instance.getClass().getName() + ".writeObject()", e.getTargetException());
        }
    }

    private Method getMethod(Class type, String name, Class[] parameterTypes, boolean includeBaseclasses) {
        Object key = type.getName() + "." + name + "." + includeBaseclasses;
        if (cache.containsKey(key)) {
            Object result = cache.get(key);
            return (Method) (result == NO_METHOD ? null : result);
        }
        if (includeBaseclasses) {
            while (type != null) {
                try {
                    Method result = type.getDeclaredMethod(name, parameterTypes);
                    result.setAccessible(true);
                    cache.put(key, result);
                    return result;
                } catch (NoSuchMethodException e) {
                    type = type.getSuperclass();
                }
            }
            cache.put(key, NO_METHOD);
            return null;
        } else {
            try {
                Method result = type.getDeclaredMethod(name, parameterTypes);
                result.setAccessible(true);
                cache.put(key, result);
                return result;
            } catch (NoSuchMethodException e) {
                cache.put(key, NO_METHOD);
                return null;
            }
        }
    }

}

@@ -63,16 +63,22 @@
 06, 2007
+, 2008
  XStream
@@ -4690,16 +4690,24 @@
 erTypes,
+%0A       
  boolean
@@ -4741,57 +4741,268 @@
     
-Object key = type.getName() + %22.%22 + name + %22.%22 + 
+final String typeName = type.getName();%0A        StringBuffer sb = new StringBuffer(typeName.length() + name.length() + 7);%0A        sb%0A            .append(typeName)%0A            .append('.')%0A            .append(name)%0A            .append('.')%0A            .append(
 incl
@@ -5015,17 +5015,17 @@
 eclasses
-;
+)
 %0A       
@@ -5029,42 +5029,61 @@
     
-if (cache.containsKey(key)) %7B%0A    
+    .toString();%0A        String key = sb.toString();%0A
     
@@ -5099,16 +5099,18 @@
 t result
+Ob
  = cache
@@ -5119,16 +5119,49 @@
 et(key);
+%0A%0A        if (resultOb != null) %7B
 %0A       
@@ -5175,25 +5175,16 @@
 turn
- (Method)
  (result
  == 
@@ -5175,24 +5175,26 @@
 turn (result
+Ob
  == NO_METHO
@@ -5194,16 +5194,17 @@
 O_METHOD
+)
  ? null 
@@ -5205,22 +5205,33 @@
  null : 
+((Method)
 result
+Ob
 );%0A     

Apply performance patch for SerializationMethodInvoker (XSTR-517).