| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| LayoutUtil |
|
| 3.9;3.9 | ||||
| LayoutUtil$1 |
|
| 3.9;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 > 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 > 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 < 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 | } |