Other |
---|
1.4.2 mantisFixed |
Duplicate :
|
|
Duplicate :
|
|
Duplicate :
|
|
Duplicate :
|
|
Relates :
|
; d.close(); System.exit(0); } } ---------- END SOURCE ---------- CUSTOMER WORKAROUND : The only workaround I have found is occluding the XMLEncoder class using -Xbootclasspath with the fix provided. (Review ID: 145688) ====================================================================== Name: gm110360 Date: 05/03/2002 FULL PRODUCT VERSION : java version "1.4.0" Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92) Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode) FULL OPERATING SYSTEM VERSION : All platforms A DESCRIPTION OF THE PROBLEM : There is a bug in the XMLEncoder which causes the encoder to create invalid archives for some graphs. There does not seem to be a simple case in which shows the error (I have spent all day looking for one). The test case enclosed, while complicated, is a useful case since it uses a set of persistence delegates to encode the SpingLayout class. I am the original author of the XMLEncoder (and made the mistake!) so I have included the fix in this report. STEPS TO FOLLOW TO REPRODUCE THE PROBLEM : 1. javac Test 2. java Test 3. EXPECTED VERSUS ACTUAL BEHAVIOR : All XML files produced by the XMLEncoder should be valid archives. The Test program uses the XMLDecoder to load the "Test.xml" file it creates. This shows the archive to be invalid. ERROR MESSAGES/STACK TRACES THAT OCCUR : java.lang.Exception: Unbound variable: SpringLayout$SpringProxy0 Continuing ... java.lang.NullPointerException Continuing ... javax.swing.JPanel [,0,0,0x0,invalid,layout=javax.swing.SpringLayout,alignmentX=null,alignmentY=nul l,border=,flags=9,maximumSize=,minimumSize=,preferredSize=] This bug can be reproduced always. ---------- BEGIN SOURCE ---------- /** * Demonstrates the XMLEncoder bug, where the XMLEncoder duplicates the * SpringLayout instance because it is required as the target * of a factory method (in proxy). * * TO FIX * * Move the first line of the XMLEncoder::mark(Statement method) * to the end of the method. I.e. replace the mark() method in * XMLEncoder with this: * private void mark(Statement stm) { Object[] args = stm.getArguments(); for (int i = 0; i < args.length; i++) { Object arg = args[i]; mark(arg, true); } mark(stm.getTarget(), false); } */ import javax.swing.*; import java.awt.*; import java.beans.*; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.lang.reflect.Field; import java.util.Iterator; import java.util.Map; public class Test { private static Class compoundSpringClass; private static Class springProxyClass; private static boolean equals(Object o1, Object o2) { return (o1 == null) ? (o2 == null) : o1.equals(o2); } static { try { compoundSpringClass = Class.forName ("javax.swing.Spring$CompoundSpring"); springProxyClass = Class.forName ("javax.swing.SpringLayout$SpringProxy"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } private static Object getPrivateField(Object instance, Class declaringClass, String name, ExceptionListener el) { try { Field f = declaringClass.getDeclaredField(name); f.setAccessible(true); return f.get(instance); } catch (Exception e) { el.exceptionThrown(e); } return null; } private static class CompoundSpringPersistenceDelegate extends PersistenceDelegate { private String functionName; // Construct with the name of the function to be used. public CompoundSpringPersistenceDelegate(String functionName) { this.functionName = functionName; } protected Expression instantiate(Object oldInstance, Encoder out) { ExceptionListener el = out.getExceptionListener(); Spring s1 = (Spring) getPrivateField(oldInstance, compoundSpringClass, "s1", el); Spring s2 = (Spring) getPrivateField(oldInstance, compoundSpringClass, "s2", el); return new Expression(oldInstance, Spring.class, functionName, new Object[]{unproxy(s1), unproxy(s2)}); } } private static JComponent getParent(Map constraints) { Iterator it = constraints.keySet().iterator(); JComponent result = (JComponent) it.next(); while (it.hasNext()) { JComponent c = (JComponent) it.next(); if (c.getParent() == result) { // Fine, result is indeed the parent. } else if (result.getParent() == c) { // Have just hit the parent. result = c; } else { throw new RuntimeException("Cannot find parent for SpringLayout."); } } return result; } private static Spring unproxy(Spring spring) { return spring; } public static void configure(Encoder e) throws Exception { final Class staticSpringClass = Class.forName ("javax.swing.Spring$StaticSpring"); // SpringLayout e.setPersistenceDelegate(SpringLayout.class, new DefaultPersistenceDelegate() { private void handleConstraints(Encoder out, Component oldComponent, Object oldInstance, Object newInstance) { Component newComponent = (Component) out.get(oldComponent); // SpringLayout.Constraints cts = (SpringLayout.Constraints) e.getValue(); Expression oldGetExp = new Expression (oldInstance, "getConstraints", new Object[]{oldComponent}); Expression newGetExp = new Expression (newInstance, "getConstraints", new Object[]{newComponent}); try { SpringLayout.Constraints oldConstraints = (SpringLayout.Constraints) oldGetExp.getValue(); SpringLayout.Constraints newConstraints = (SpringLayout.Constraints) newGetExp.getValue(); out.writeExpression(oldGetExp); if (!Test.equals(newConstraints, out.get(oldConstraints))) { out.writeStatement(new Statement (oldInstance, "addLayoutComponent", new Object[]{oldComponent, oldConstraints})); } } catch (Exception e1) { System.out.println("Failed here ... "); out.getExceptionListener().exceptionThrown(e1); } } protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) { SpringLayout oldSpringLayout = (SpringLayout) oldInstance; SpringLayout newSpringLayout = (SpringLayout) newInstance; Map oldConstraintsMap = (Map) getPrivateField(oldInstance, SpringLayout.class, "componentConstraints", out.getExceptionListener()); JComponent oldParent = getParent(oldConstraintsMap); JComponent newParent = (JComponent) out.get(oldParent); // The SpringLayout manager performs some initialization // the first time any of, get{Preferred, {Min, Max}imum}Size, // or layoutContainer is called. Call this here to make // sure any internal state is consistent. oldSpringLayout.layoutContainer(oldParent); if (newSpringLayout != null && newParent != null) { newSpringLayout.layoutContainer(newParent); } // Do the parent component first. // handleConstraints(out, oldParent, oldInstance, newInstance); Iterator it = oldConstraintsMap.keySet().iterator(); while (it.hasNext()) { Component oldComponent = (Component) it.next(); if (oldComponent != oldParent) { handleConstraints(out, oldComponent, oldInstance, newInstance); } } } }); // Spring proxies. e.setPersistenceDelegate(springProxyClass, new PersistenceDelegate() { protected Expression instantiate(Object oldInstance, Encoder out) { ExceptionListener el = out.getExceptionListener(); Object edgeName = getPrivateField(oldInstance, springProxyClass, "edgeName", el); Object c = getPrivateField(oldInstance, springProxyClass, "c", el); Object l = getPrivateField(oldInstance, springProxyClass, "l", el); return new Expression(oldInstance, l, "getConstraint", new Object[]{edgeName, c}); } // The above delegate highlights a bug in the archiver. // It is not possible to initialise an instance with subgraph // of components that use a factory method on the instance // being initialised. The archiver incorrectly assumes that // such a factory method will be called in the context of // the enclosing instance (the one that is being used as // the factory) and therefore that it will not need an // id. It does in this case as the expression is part of // the initialisation of the factor oobject. Difficult to // explain. // // For now short cut all proxies when the archive is // being written. }); // Spring.Constraints. // Width and Height Springs are installed automatically by the layout manager when the // constraints object is underconstrained. Check this case and treat instances // of the private Height and Width Spring classes like null. final Class heightSpringClass = Class.forName ("javax.swing.SpringLayout$HeightSpring"); final Class widthSpringClass = Class.forName ("javax.swing.SpringLayout$WidthSpring"); // Use private fields to initialise the SpringLayout as, for example, the // public x, width and east properties of a constraints object depend on each // other. Also, the east property, for example, is set using a non- stnadard idiom. e.setPersistenceDelegate(SpringLayout.Constraints.class, new DefaultPersistenceDelegate() { protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) { ExceptionListener el = out.getExceptionListener(); String properties[] = {"X", "Y", "Width", "Height", "East", "South"}; for (int i = 0; i < properties.length; i++) { String pName = properties[i]; Spring s = (Spring) getPrivateField(oldInstance, SpringLayout.Constraints.class, pName.toLowerCase(), el); if (s != null && s.getClass() != heightSpringClass && s.getClass() != widthSpringClass) { Expression oldGetExp; Expression newGetExp; if (pName != "East" && pName != "South") { oldGetExp = new Expression(oldInstance, "get" + pName, new Object[]{}); newGetExp = new Expression(newInstance, "get" + pName, new Object[]{}); } else { oldGetExp = new Expression (oldInstance, "getConstraint", new Object[]{pName}); newGetExp = new Expression (newInstance, "getConstraint", new Object[]{pName}); } try { Spring oldSpring = (Spring) oldGetExp.getValue(); Spring newSpring = (Spring) newGetExp.getValue(); // out.writeExpression(oldGetExp); if (!Test.equals(newSpring, out.get(oldSpring))) { if (pName != "East" && pName != "South") { out.writeStatement(new Statement (oldInstance, "set" + pName, new Object[]{s})); } else { out.writeStatement(new Statement (oldInstance, "setConstraint", new Object[]{pName, s})); } } } catch (Exception e1) { System.out.println("Failed here ... "); out.getExceptionListener().exceptionThrown(e1); } } } } }); // Spring constants. e.setPersistenceDelegate(staticSpringClass, new PersistenceDelegate() { protected Expression instantiate(Object oldInstance, Encoder out) { Spring s = (Spring) oldInstance; int min = s.getMinimumValue(); int pref = s.getPreferredValue(); int max = s.getMaximumValue(); Object[] args = null; // Use the shorter static method if possible. if (min == pref && pref == max) { args = new Object[]{new Integer(pref)}; } else { args = new Object[]{new Integer(min), new Integer(pref), new Integer(max)}; } return new Expression(oldInstance, Spring.class, "constant", args); } }); // Negative Springs. final Class negativeSpringClass = Class.forName ("javax.swing.Spring$NegativeSpring"); e.setPersistenceDelegate(negativeSpringClass, new PersistenceDelegate() { protected Expression instantiate(Object oldInstance, Encoder out) { ExceptionListener el = out.getExceptionListener(); Spring s = (Spring) getPrivateField(oldInstance, negativeSpringClass, "s", el); return new Expression(oldInstance, Spring.class, "minus", new Object[]{unproxy(s)}); } }); // Spring sums. e.setPersistenceDelegate(Class.forName("javax.swing.Spring$SumSpring"), new CompoundSpringPersistenceDelegate("sum")); // Spring maxima. e.setPersistenceDelegate(Class.forName("javax.swing.Spring$MaxSpring"), new CompoundSpringPersistenceDelegate("max")); } public static void main(String[] args) throws Exception { JPanel panel = new JPanel(new SpringLayout()); JButton button = new JButton(); panel.add(button); SpringLayout l = (SpringLayout) panel.getLayout(); // Force getParent() to be called in the layout manager. l.layoutContainer(panel); l.putConstraint("South", button, -20, "South", panel); XMLEncoder e = new XMLEncoder( new BufferedOutputStream( new FileOutputStream("Test.xml"))); configure(e); e.writeObject(panel); e.close(); // Read file back in to demonstrate that it is invalid. XMLDecoder d = new XMLDecoder( new BufferedInputStream( new FileInputStream("Test.xml"))); Object root = d.readObject(); System.out.println(root)
|