Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4679556

XMLEncoder can be configured to produce invalid archives

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: P3
    • Resolution: Fixed
    • Affects Version/s: 1.4.0, 1.4.1
    • Fix Version/s: 1.4.2
    • Component/s: client-libs
    • Labels:
    • Subcomponent:
    • Resolved In Build:
      mantis
    • CPU:
      x86
    • OS:
      linux, windows_nt, windows_2000

      Description

      ;
              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)

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              mdavidsosunw Mark Davidson (Inactive)
              Reporter:
              gmanwanisunw Girish Manwani (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved:
                Imported:
                Indexed: