package org.limewire.util; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Converts older package and class names to the new equivalent name and is useful * in code refactoring. For example, if packages or classes are renamed, * <code>ObjectInputStream</code> fails to find * the older name. <code>ConverterObjectInputStream</code> looks up the old name * and if a new name exists, <code>ConverterObjectInputStream</code> internally * updates any references to the new name without the old name needing to be in the * classpath (and as long as serialVersionUID is the same; see {@link Serializable} * for more information). * <p> * <code>ConverterObjectInputStream</code> comes with a pre-set package and class * mapping between the old and new name, and you can add more lookups with * {@link #addLookup(String, String)}. * <p> * Pre-set package and class mapping: * <table cellpadding="5"> * <tr> * <td><b>Old Name</b></td> * <td><b>New Name</b></td> * </tr><tr> * <td>com.limegroup.gnutella.util.FileComparator</td> * <td>org.limewire.collection.FileComparator</td> * </tr> * <tr> * <td>com.limegroup.gnutella.downloader.Interval</td> * <td>org.limewire.collection.Interval</td> * </tr> * <tr> * <td>com.limegroup.gnutella.util.IntervalSet</td> * <td> org.limewire.collection.IntervalSet</td> * </tr> * <tr> * <td>com.limegroup.gnutella.util.Comparators$CaseInsensitiveStringComparator</td> * <td> org.limewire.collection.Comparators$CaseInsensitiveStringComparator</td> * </tr><tr> * <td>com.limegroup.gnutella.util.StringComparator</td> * <td> org.limewire.collection.StringComparator</td> * </tr> * <tr> * <td>com.sun.java.util.collections</td> * <td>java.util</td> * </tr> * </table> * None of the earlier forms of the class need to exist in the classpath. */ public class ConverterObjectInputStream extends ObjectInputStream { private static final Log LOG = LogFactory.getLog(ConverterObjectInputStream.class); private Map<String, String> lookups = new HashMap<String, String>(8); /** * Constructs a new <code>ConverterObjectInputStream</code> wrapping the * specified <code>InputStream</code>. */ public ConverterObjectInputStream(InputStream in) throws IOException { super(in); createLookups(); } /** * Erases any lookups that were added using {@link #addLookup(String, String)}. */ public void revertToDefault() { lookups.clear(); createLookups(); } /** Adds all internal lookups. */ private void createLookups() { lookups.put("com.limegroup.gnutella.util.FileComparator", "org.limewire.collection.FileComparator"); lookups.put("com.limegroup.gnutella.util.Comparators$CaseInsensitiveStringComparator", "org.limewire.collection.Comparators$CaseInsensitiveStringComparator"); lookups.put("com.limegroup.gnutella.util.StringComparator", "org.limewire.collection.StringComparator"); lookups.put("com.sun.java.util.collections", "java.util"); lookups.put("com.limegroup.gnutella.MediaType", "org.limewire.util.MediaType"); } /** * Adds a mapping between an old package or class name to a new name. * @param oldName the name of the old package or class * @param newName the name of the new package or class */ public void addLookup(String oldName, String newName) { lookups.put(oldName, newName); } /** * Overridden to manually alter the class descriptors. * Note this does NOT require the original class to be loadable. * <p> * Lookup works as follows: * <ul> * <li>The serialized (old) class name is looked up, if a corresponding new * class name exists the <code>ObjectStreamClass</code> object for it is returned.</li> * <li>The package name of the serialized class name is extracted and * looked up if a new package name exists, it is prepended to the name of * the class the corresponding class is loaded.</li> * <li>Otherwise the original ObjectStreamClass is returned.</li> * <ul> */ @Override protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { ObjectStreamClass read = super.readClassDescriptor(); String className = read.getName(); if(LOG.isDebugEnabled()) LOG.debug("Looking up class: " + className); boolean array = className.startsWith("[L") && className.endsWith(";"); if(array) { className = className.substring(2, className.length()-1); if(LOG.isDebugEnabled()) LOG.debug("Stripping array form off, resulting in: " + className); } ObjectStreamClass clazzToReturn = null; String newName = lookups.get(className); if (newName != null) { clazzToReturn = ObjectStreamClass.lookup(Class.forName(newName)); } else if(className.contains("$")) { // If it's an inner class, replace the parent. int index = className.indexOf("$"); String newParent = lookups.get(className.substring(0, index)); if(newParent != null) { clazzToReturn = ObjectStreamClass.lookup(Class.forName(newParent + className.substring(index))); } } if(clazzToReturn == null) { int index = className.lastIndexOf('.'); // use "" as lookup key for default package String oldPackage = index != -1 ? className.substring(0, index) : ""; String newPackage = lookups.get(oldPackage); if (newPackage != null) { if (newPackage.length() == 0) { // mapped to default package clazzToReturn = ObjectStreamClass.lookup(Class.forName(className.substring(index + 1))); } else { clazzToReturn = ObjectStreamClass.lookup(Class.forName(newPackage + '.' + className.substring(index + 1))); } } else { // Nothing it maps to -- must be the real name! clazzToReturn = read; } } if(LOG.isDebugEnabled() && clazzToReturn != read) LOG.debug("Located substitute class: " + clazzToReturn.getName()); // If it's an array, and we modified we we read off disk, convert // to array form. if(array && read != clazzToReturn) { clazzToReturn = ObjectStreamClass.lookup(Class.forName("[L" + clazzToReturn.getName() + ";")); if(LOG.isDebugEnabled()) LOG.debug("Re-added array wrapper, for class: " + clazzToReturn.getName()); } return clazzToReturn; } }