Coverage Report - net.miginfocom.layout.LayoutUtil
 
Classes in this File Line Coverage Branch Coverage Complexity
LayoutUtil
N/A
N/A
3.9
LayoutUtil$1
N/A
N/A
3.9
 
 1  
 package net.miginfocom.layout;
 2  
 
 3  
 import java.beans.*;
 4  
 import java.io.*;
 5  
 import java.util.IdentityHashMap;
 6  
 import java.util.TreeSet;
 7  
 import java.util.WeakHashMap;
 8  
 /*
 9  
  * License (BSD):
 10  
  * ==============
 11  
  *
 12  
  * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
 13  
  * All rights reserved.
 14  
  *
 15  
  * Redistribution and use in source and binary forms, with or without modification,
 16  
  * are permitted provided that the following conditions are met:
 17  
  * Redistributions of source code must retain the above copyright notice, this list
 18  
  * of conditions and the following disclaimer.
 19  
  * Redistributions in binary form must reproduce the above copyright notice, this
 20  
  * list of conditions and the following disclaimer in the documentation and/or other
 21  
  * materials provided with the distribution.
 22  
  * Neither the name of the MiG InfoCom AB nor the names of its contributors may be
 23  
  * used to endorse or promote products derived from this software without specific
 24  
  * prior written permission.
 25  
  *
 26  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 27  
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 28  
  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 29  
  * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 30  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 31  
  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 32  
  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 33  
  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 34  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 35  
  * OF SUCH DAMAGE.
 36  
  *
 37  
  * @version 1.0
 38  
  * @author Mikael Grev, MiG InfoCom AB
 39  
  *         Date: 2006-sep-08
 40  
  */
 41  
 
 42  
 /** A utility class that has only static helper methods.
 43  
  */
 44  
 public final class LayoutUtil
 45  
 {
 46  
         /** A substitute value for aa really large value. Integer.MAX_VALUE is not used since that means a lot of defensive code
 47  
          * for potential overflow must exist in many places. This value is large enough for being unreasonable yet it is hard to
 48  
          * overflow.
 49  
          */
 50  
         public static final int INF = (Integer.MAX_VALUE >> 10) - 100; // To reduce likelihood of overflow errors when calculating.
 51  
 
 52  
         /** Tag int for a value that in considered "not set". Used as "null" element in int arrays.
 53  
          */
 54  
         static final int NOT_SET = Integer.MIN_VALUE + 12346;   // Magic value...
 55  
 
 56  
         // Index for the different sizes
 57  
         public static final int MIN = 0;
 58  
         public static final int PREF = 1;
 59  
         public static final int MAX = 2;
 60  
 
 61  
         private static volatile WeakHashMap<Object, String> CR_MAP = null;
 62  
         private static volatile WeakHashMap<Object, Boolean> DT_MAP = null;      // The Containers that have design time. Value not used.
 63  
         private static int eSz = 0;
 64  
         private static int globalDebugMillis = 0;
 65  
     public static final boolean HAS_BEANS = hasBeans();
 66  
 
 67  
     private static boolean hasBeans()
 68  
     {
 69  
         try {
 70  
             LayoutUtil.class.getClassLoader().loadClass("java.beans.Beans");
 71  
             return true;
 72  
         } catch (ClassNotFoundException e) {
 73  
             return false;
 74  
         }
 75  
     }
 76  
 
 77  
         private LayoutUtil()
 78  
         {
 79  
         }
 80  
 
 81  
         /** Returns the current version of MiG Layout.
 82  
          * @return The current version of MiG Layout. E.g. "3.6.3" or "4.0"
 83  
          */
 84  
         public static String getVersion()
 85  
         {
 86  
                 return "4.0";
 87  
         }
 88  
 
 89  
         /** If global debug should be on or off. If &gt; 0 then debug is turned on for all MigLayout
 90  
          * instances.
 91  
          * @return The current debug milliseconds.
 92  
          * @see LC#setDebugMillis(int)
 93  
          */
 94  
         public static int getGlobalDebugMillis()
 95  
         {
 96  
                 return globalDebugMillis;
 97  
         }
 98  
 
 99  
         /** If global debug should be on or off. If &gt; 0 then debug is turned on for all MigLayout
 100  
          * instances.
 101  
          * <p>
 102  
          * Note! This is a passive value and will be read by panels when the needed, which is normally
 103  
          * when they repaint/layout.
 104  
          * @param millis The new debug milliseconds. 0 turns of global debug and leaves debug up to every
 105  
          * individual panel.
 106  
          * @see LC#setDebugMillis(int)
 107  
          */
 108  
         public static void setGlobalDebugMillis(int millis)
 109  
         {
 110  
                 globalDebugMillis = millis;
 111  
         }
 112  
 
 113  
         /** Sets if design time is turned on for a Container in {@link ContainerWrapper}.
 114  
          * @param cw The container to set design time for. <code>null</code> is legal and can be used as
 115  
          * a key to turn on/off design time "in general". Note though that design time "in general" is
 116  
          * always on as long as there is at least one ContainerWrapper with design time.
 117  
          * <p>
 118  
          * <strong>If this method has not ever been called it will default to what
 119  
          * <code>Beans.isDesignTime()</code> returns.</strong> This means that if you call
 120  
          * this method you indicate that you will take responsibility for the design time value.
 121  
          * @param b <code>true</code> means design time on.
 122  
          */
 123  
         public static void setDesignTime(ContainerWrapper cw, boolean b)
 124  
         {
 125  
                 if (DT_MAP == null)
 126  
                         DT_MAP = new WeakHashMap<Object, Boolean>();
 127  
 
 128  
                 DT_MAP.put((cw != null ? cw.getComponent() : null), b);
 129  
         }
 130  
 
 131  
         /** Returns if design time is turned on for a Container in {@link ContainerWrapper}.
 132  
          * @param cw The container to set design time for. <code>null</code> is legal will return <code>true</code>
 133  
          * if there is at least one <code>ContainerWrapper</code> (or <code>null</code>) that have design time
 134  
          * turned on.
 135  
          * @return If design time is set for <code>cw</code>.
 136  
          */
 137  
         public static boolean isDesignTime(ContainerWrapper cw)
 138  
         {
 139  
                 if (DT_MAP == null)
 140  
                         return HAS_BEANS && Beans.isDesignTime();
 141  
 
 142  
                 if (cw != null && DT_MAP.containsKey(cw.getComponent()) == false)
 143  
                         cw = null;
 144  
 
 145  
                 Boolean b = DT_MAP.get(cw != null ? cw.getComponent() : null);
 146  
                 return b != null && b;
 147  
         }
 148  
 
 149  
         /** The size of an empty row or columns in a grid during design time.
 150  
          * @return The number of pixels. Default is 15.
 151  
          */
 152  
         public static int getDesignTimeEmptySize()
 153  
         {
 154  
                 return eSz;
 155  
         }
 156  
 
 157  
         /** The size of an empty row or columns in a grid during design time.
 158  
          * @param pixels The number of pixels. Default is 0 (it was 15 prior to v3.7.2, but since that meant different behaviour
 159  
          * under design time by default it was changed to be 0, same as non-design time). IDE vendors can still set it to 15 to
 160  
          * get the old behaviour.
 161  
          */
 162  
         public static void setDesignTimeEmptySize(int pixels)
 163  
         {
 164  
                 eSz = pixels;
 165  
         }
 166  
 
 167  
         /** Associates <code>con</code> with the creation string <code>s</code>. The <code>con</code> object should
 168  
          * probably have an equals method that compares identities or <code>con</code> objects that .equals() will only
 169  
          * be able to have <b>one</b> creation string.
 170  
          * <p>
 171  
          * If {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns <code>false</code> the method does nothing.
 172  
          * @param con The object. if <code>null</code> the method does nothing.
 173  
          * @param s The creation string. if <code>null</code> the method does nothing.
 174  
          */
 175  
         static void putCCString(Object con, String s)
 176  
         {
 177  
                 if (s != null && con != null && isDesignTime(null)) {
 178  
                         if (CR_MAP == null)
 179  
                                 CR_MAP = new WeakHashMap<Object, String>(64);
 180  
 
 181  
                         CR_MAP.put(con, s);
 182  
                 }
 183  
         }
 184  
 
 185  
         /** Sets/add the persistence delegates to be used for a class.
 186  
          * @param c The class to set the registered deligate for.
 187  
          * @param del The new delegate or <code>null</code> to erase to old one.
 188  
          */
 189  
         static synchronized void setDelegate(Class c, PersistenceDelegate del)
 190  
         {
 191  
                 try {
 192  
                         Introspector.getBeanInfo(c, Introspector.IGNORE_ALL_BEANINFO).getBeanDescriptor().setValue("persistenceDelegate", del);
 193  
                 } catch (Exception ignored) {
 194  
                 }
 195  
         }
 196  
 
 197  
         /** Returns strings set with {@link #putCCString(Object, String)} or <code>null</code> if nothing is associated or
 198  
          * {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns <code>false</code>.
 199  
          * @param con The constrain object.
 200  
          * @return The creation string or <code>null</code> if nothing is registered with the <code>con</code> object.
 201  
          */
 202  
         static String getCCString(Object con)
 203  
         {
 204  
                 return CR_MAP != null ? CR_MAP.get(con) : null;
 205  
         }
 206  
 
 207  
         static void throwCC()
 208  
         {
 209  
                 throw new IllegalStateException("setStoreConstraintData(true) must be set for strings to be saved.");
 210  
         }
 211  
 
 212  
         /** Takes a number on min/preferred/max sizes and resize constraints and returns the calculated sizes which sum should add up to <code>bounds</code>. Whether the sum
 213  
          * will actually equal <code>bounds</code> is dependent om the pref/max sizes and resize constraints.
 214  
          * @param sizes [ix],[MIN][PREF][MAX]. Grid.CompWrap.NOT_SET will be treated as N/A or 0. A "[MIN][PREF][MAX]" array with null elements will be interpreted as very flexible (no bounds)
 215  
          * but if the array itself is null it will not get any size.
 216  
          * @param resConstr Elements can be <code>null</code> and the whole array can be <code>null</code>. <code>null</code> means that the size will not be flexible at all.
 217  
          * Can have length less than <code>sizes</code> in which case the last element should be used for the elements missing.
 218  
          * @param defPushWeights If there is no grow weight for a resConstr the corresponding value of this array is used.
 219  
          * These forced resConstr will be grown last though and only if needed to fill to the bounds.
 220  
          * @param startSizeType The initial size to use. E.g. {@link net.miginfocom.layout.LayoutUtil#MIN}.
 221  
          * @param bounds To use for relative sizes.
 222  
          * @return The sizes. Array length will match <code>sizes</code>.
 223  
          */
 224  
         static int[] calculateSerial(int[][] sizes, ResizeConstraint[] resConstr, Float[] defPushWeights, int startSizeType, int bounds)
 225  
         {
 226  
                 float[] lengths = new float[sizes.length];        // heights/widths that are set
 227  
                 float usedLength = 0.0f;
 228  
 
 229  
                 // Give all preferred size to start with
 230  
                 for (int i = 0; i < sizes.length; i++) {
 231  
                         if (sizes[i] != null) {
 232  
                                 float len = sizes[i][startSizeType] != NOT_SET ? sizes[i][startSizeType] : 0;
 233  
                                 int newSizeBounded = getBrokenBoundary(len, sizes[i][MIN], sizes[i][MAX]);
 234  
                                 if (newSizeBounded != NOT_SET)
 235  
                                         len = newSizeBounded;
 236  
 
 237  
                                 usedLength += len;
 238  
                                 lengths[i] = len;
 239  
                         }
 240  
                 }
 241  
 
 242  
                 int useLengthI = Math.round(usedLength);
 243  
                 if (useLengthI != bounds && resConstr != null) {
 244  
                         boolean isGrow = useLengthI < bounds;
 245  
 
 246  
                         // Create a Set with the available priorities
 247  
                         TreeSet<Integer> prioList = new TreeSet<Integer>();
 248  
                         for (int i = 0; i < sizes.length; i++) {
 249  
                                 ResizeConstraint resC = (ResizeConstraint) getIndexSafe(resConstr, i);
 250  
                                 if (resC != null)
 251  
                                         prioList.add(isGrow ? resC.growPrio : resC.shrinkPrio);
 252  
                         }
 253  
                         Integer[] prioIntegers = prioList.toArray(new Integer[prioList.size()]);
 254  
 
 255  
                         for (int force = 0; force <= ((isGrow && defPushWeights != null) ? 1 : 0); force++) {    // Run twice if defGrow and the need for growing.
 256  
                                 for (int pr = prioIntegers.length - 1; pr >= 0; pr--) {
 257  
                                         int curPrio = prioIntegers[pr];
 258  
 
 259  
                                         float totWeight = 0f;
 260  
                                         Float[] resizeWeight = new Float[sizes.length];
 261  
                                         for (int i = 0; i < sizes.length; i++) {
 262  
                                                 if (sizes[i] == null)   // if no min/pref/max size at all do not grow or shrink.
 263  
                                                         continue;
 264  
 
 265  
                                                 ResizeConstraint resC = (ResizeConstraint) getIndexSafe(resConstr, i);
 266  
                                                 if (resC != null) {
 267  
                                                         int prio = isGrow ? resC.growPrio : resC.shrinkPrio;
 268  
 
 269  
                                                         if (curPrio == prio) {
 270  
                                                                 if (isGrow) {
 271  
                                                                         resizeWeight[i] = (force == 0 || resC.grow != null) ? resC.grow : (defPushWeights[i < defPushWeights.length ? i : defPushWeights.length - 1]);
 272  
                                                                 } else {
 273  
                                                                         resizeWeight[i] = resC.shrink;
 274  
                                                                 }
 275  
                                                                 if (resizeWeight[i] != null)
 276  
                                                                         totWeight += resizeWeight[i];
 277  
                                                         }
 278  
                                                 }
 279  
                                         }
 280  
 
 281  
                                         if (totWeight > 0f) {
 282  
                                                 boolean hit;
 283  
                                                 do {
 284  
                                                         float toChange = bounds - usedLength;
 285  
                                                         hit = false;
 286  
                                                         float changedWeight = 0f;
 287  
                                                         for (int i = 0; i < sizes.length && totWeight > 0.0001f; i++) {
 288  
 
 289  
                                                                 Float weight = resizeWeight[i];
 290  
                                                                 if (weight != null) {
 291  
                                                                         float sizeDelta = toChange * weight / totWeight;
 292  
                                                                         float newSize = lengths[i] + sizeDelta;
 293  
 
 294  
                                                                         if (sizes[i] != null) {
 295  
                                                                                 int newSizeBounded = getBrokenBoundary(newSize, sizes[i][MIN], sizes[i][MAX]);
 296  
                                                                                 if (newSizeBounded != NOT_SET) {
 297  
                                                                                         resizeWeight[i] = null;
 298  
                                                                                         hit = true;
 299  
                                                                                         changedWeight += weight;
 300  
                                                                                         newSize = newSizeBounded;
 301  
                                                                                         sizeDelta = newSize - lengths[i];
 302  
                                                                                 }
 303  
                                                                         }
 304  
 
 305  
                                                                         lengths[i] = newSize;
 306  
                                                                         usedLength += sizeDelta;
 307  
                                                                 }
 308  
                                                         }
 309  
                                                         totWeight -= changedWeight;
 310  
                                                 } while (hit);
 311  
                                         }
 312  
                                 }
 313  
                         }
 314  
                 }
 315  
                 return roundSizes(lengths);
 316  
         }
 317  
 
 318  
         static Object getIndexSafe(Object[] arr, int ix)
 319  
         {
 320  
                 return arr != null ? arr[ix < arr.length ? ix : arr.length - 1] : null;
 321  
         }
 322  
 
 323  
         /** Returns the broken boundary if <code>sz</code> is outside the boundaries <code>lower</code> or <code>upper</code>. If both boundaries
 324  
          * are broken, the lower one is returned. If <code>sz</code> is &lt; 0 then <code>new Float(0f)</code> is returned so that no sizes can be
 325  
          * negative.
 326  
          * @param sz The size to check
 327  
          * @param lower The lower boundary (or <code>null</code> fo no boundary).
 328  
          * @param upper The upper boundary (or <code>null</code> fo no boundary).
 329  
          * @return The broken boundary.
 330  
          */
 331  
         private static int getBrokenBoundary(float sz, int lower, int upper)
 332  
         {
 333  
                 if (lower != NOT_SET) {
 334  
                         if (sz < lower)
 335  
                                 return lower;
 336  
                 } else if (sz < 0f) {
 337  
                         return 0;
 338  
                 }
 339  
 
 340  
                 if (upper != NOT_SET && sz > upper)
 341  
                         return upper;
 342  
 
 343  
                 return NOT_SET;
 344  
         }
 345  
 
 346  
 
 347  
         static int sum(int[] terms, int start, int len)
 348  
         {
 349  
                 int s = 0;
 350  
                 for (int i = start, iSz = start + len; i < iSz; i++)
 351  
                         s += terms[i];
 352  
                 return s;
 353  
         }
 354  
 
 355  
         static int sum(int[] terms)
 356  
         {
 357  
                 return sum(terms, 0, terms.length);
 358  
         }
 359  
 
 360  
         public static int getSizeSafe(int[] sizes, int sizeType)
 361  
         {
 362  
                 if (sizes == null || sizes[sizeType] == NOT_SET)
 363  
                         return sizeType == MAX ? LayoutUtil.INF : 0;
 364  
                 return sizes[sizeType];
 365  
         }
 366  
 
 367  
         static BoundSize derive(BoundSize bs, UnitValue min, UnitValue pref, UnitValue max)
 368  
         {
 369  
                 if (bs == null || bs.isUnset())
 370  
                         return new BoundSize(min, pref, max, null);
 371  
 
 372  
                 return new BoundSize(
 373  
                                 min != null ? min : bs.getMin(),
 374  
                                 pref != null ? pref : bs.getPreferred(),
 375  
                                 max != null ? max : bs.getMax(),
 376  
                                 bs.getGapPush(),
 377  
                                 null);
 378  
         }
 379  
 
 380  
         /** Returns if left-to-right orientation is used. If not set explicitly in the layout constraints the Locale
 381  
          * of the <code>parent</code> is used.
 382  
          * @param lc The constraint if there is one. Can be <code>null</code>.
 383  
          * @param container The parent that may be used to get the left-to-right if ffc does not specify this.
 384  
          * @return If left-to-right orientation is currently used.
 385  
          */
 386  
         public static boolean isLeftToRight(LC lc, ContainerWrapper container)
 387  
         {
 388  
                 if (lc != null && lc.getLeftToRight() != null)
 389  
                         return lc.getLeftToRight();
 390  
 
 391  
                 return container == null || container.isLeftToRight();
 392  
         }
 393  
 
 394  
         /** Round a number of float sizes into int sizes so that the total length match up
 395  
          * @param sizes The sizes to round
 396  
          * @return An array of equal length as <code>sizes</code>.
 397  
          */
 398  
         static int[] roundSizes(float[] sizes)
 399  
         {
 400  
                 int[] retInts = new int[sizes.length];
 401  
                 float posD = 0;
 402  
 
 403  
                 for (int i = 0; i < retInts.length; i++) {
 404  
                         int posI = (int) (posD + 0.5f);
 405  
 
 406  
                         posD += sizes[i];
 407  
 
 408  
                         retInts[i] = (int) (posD + 0.5f) - posI;
 409  
                 }
 410  
 
 411  
                 return retInts;
 412  
         }
 413  
 
 414  
         /** Safe equals. null == null, but null never equals anything else.
 415  
          * @param o1 The first object. May be <code>null</code>.
 416  
          * @param o2 The second object. May be <code>null</code>.
 417  
          * @return Returns <code>true</code> if <code>o1</code> and <code>o2</code> are equal (using .equals()) or both are <code>null</code>.
 418  
          */
 419  
         static boolean equals(Object o1, Object o2)
 420  
         {
 421  
                 return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2));
 422  
         }
 423  
 
 424  
 //        static int getBaselineCorrect(Component comp)
 425  
 //        {
 426  
 //                Dimension pSize = comp.getPreferredSize();
 427  
 //                int baseline = comp.getBaseline(pSize.width, pSize.height);
 428  
 //                int nextBaseline = comp.getBaseline(pSize.width, pSize.height + 1);
 429  
 //
 430  
 //                // Amount to add to height when calculating where baseline
 431  
 //                // lands for a particular height:
 432  
 //                int padding = 0;
 433  
 //
 434  
 //                // Where the baseline is relative to the mid point
 435  
 //                int baselineOffset = baseline - pSize.height / 2;
 436  
 //                if (pSize.height % 2 == 0 && baseline != nextBaseline) {
 437  
 //                        padding = 1;
 438  
 //                } else if (pSize.height % 2 == 1 && baseline == nextBaseline) {
 439  
 //                        baselineOffset--;
 440  
 //                        padding = 1;
 441  
 //                }
 442  
 //
 443  
 //                // The following calculates where the baseline lands for
 444  
 //                // the height z:
 445  
 //                return (pSize.height + padding) / 2 + baselineOffset;
 446  
 //        }
 447  
 
 448  
 
 449  
         /** Returns the inset for the side.
 450  
          * @param side top == 0, left == 1, bottom = 2, right = 3.
 451  
          * @param getDefault If <code>true</code> the default insets will get retrieved if <code>lc</code> has none set.
 452  
          * @return The inset for the side. Never <code>null</code>.
 453  
          */
 454  
         static UnitValue getInsets(LC lc, int side, boolean getDefault)
 455  
         {
 456  
                 UnitValue[] i = lc.getInsets();
 457  
                 return (i != null && i[side] != null) ? i[side] : (getDefault ? PlatformDefaults.getPanelInsets(side) : UnitValue.ZERO);
 458  
         }
 459  
 
 460  
         /** Writes the objet and CLOSES the stream. Uses the persistence delegate registered in this class.
 461  
          * @param os The stream to write to. Will be closed.
 462  
          * @param o The object to be serialized.
 463  
          * @param listener The listener to recieve the exeptions if there are any. If <code>null</code> not used.
 464  
          */
 465  
         static void writeXMLObject(OutputStream os, Object o, ExceptionListener listener)
 466  
         {
 467  
                 ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
 468  
                 Thread.currentThread().setContextClassLoader(LayoutUtil.class.getClassLoader());
 469  
 
 470  
                 XMLEncoder encoder = new XMLEncoder(os);
 471  
 
 472  
                 if (listener != null)
 473  
                         encoder.setExceptionListener(listener);
 474  
 
 475  
                 encoder.writeObject(o);
 476  
         encoder.close();    // Must be closed to write.
 477  
 
 478  
                 Thread.currentThread().setContextClassLoader(oldClassLoader);
 479  
         }
 480  
 
 481  
         private static ByteArrayOutputStream writeOutputStream = null;
 482  
         /** Writes an object to XML.
 483  
          * @param out The boject out to write to. Will not be closed.
 484  
          * @param o The object to write.
 485  
          */
 486  
         public static synchronized void writeAsXML(ObjectOutput out, Object o) throws IOException
 487  
         {
 488  
                 if (writeOutputStream == null)
 489  
                         writeOutputStream = new ByteArrayOutputStream(16384);
 490  
 
 491  
                 writeOutputStream.reset();
 492  
 
 493  
                 writeXMLObject(writeOutputStream, o, new ExceptionListener() {
 494  
                         public void exceptionThrown(Exception e) {
 495  
                                 e.printStackTrace();
 496  
                         }});
 497  
 
 498  
                 byte[] buf = writeOutputStream.toByteArray();
 499  
 
 500  
                 out.writeInt(buf.length);
 501  
                 out.write(buf);
 502  
         }
 503  
 
 504  
         private static byte[] readBuf = null;
 505  
         /** Reads an object from <code>in</code> using the
 506  
          * @param in The object input to read from.
 507  
          * @return The object. Never <code>null</code>.
 508  
          * @throws IOException If there was a problem saving as XML
 509  
          */
 510  
         public static synchronized Object readAsXML(ObjectInput in) throws IOException
 511  
         {
 512  
                 if (readBuf == null)
 513  
                         readBuf = new byte[16384];
 514  
 
 515  
                 Thread cThread = Thread.currentThread();
 516  
                 ClassLoader oldCL = null;
 517  
 
 518  
                 try {
 519  
                         oldCL = cThread.getContextClassLoader();
 520  
                         cThread.setContextClassLoader(LayoutUtil.class.getClassLoader());
 521  
                 } catch(SecurityException ignored) {
 522  
                 }
 523  
 
 524  
                 Object o = null;
 525  
                 try {
 526  
                         int length = in.readInt();
 527  
                         if (length > readBuf.length)
 528  
                                 readBuf = new byte[length];
 529  
 
 530  
                         in.readFully(readBuf, 0, length);
 531  
 
 532  
                         o = new XMLDecoder(new ByteArrayInputStream(readBuf, 0, length)).readObject();
 533  
 
 534  
                 } catch(EOFException ignored) {
 535  
                 }
 536  
 
 537  
                 if (oldCL != null)
 538  
                         cThread.setContextClassLoader(oldCL);
 539  
 
 540  
                 return o;
 541  
         }
 542  
 
 543  
         private static final IdentityHashMap<Object, Object> SER_MAP = new IdentityHashMap<Object, Object>(2);
 544  
 
 545  
         /** Sets the serialized object and associates it with <code>caller</code>.
 546  
          * @param caller The object created <code>o</code>
 547  
          * @param o The just serialized object.
 548  
          */
 549  
         public static void setSerializedObject(Object caller, Object o)
 550  
         {
 551  
                 synchronized(SER_MAP) {
 552  
                         SER_MAP.put(caller, o);
 553  
                 }
 554  
         }
 555  
 
 556  
         /** Returns the serialized object that are associated with <code>caller</code>. It also removes it from the list.
 557  
          * @param caller The original creator of the object.
 558  
          * @return The object.
 559  
          */
 560  
         public static Object getSerializedObject(Object caller)
 561  
         {
 562  
                 synchronized(SER_MAP) {
 563  
                         return SER_MAP.remove(caller);
 564  
                 }
 565  
         }
 566  
 }