1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.util;
9 
10 import java.io.BufferedReader;
11 import java.io.Closeable;
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.FileReader;
15 import java.io.FileOutputStream;
16 import java.io.FileWriter;
17 import java.io.FilenameFilter;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.io.OutputStream;
23 import java.text.NumberFormat;
24 import java.util.ArrayList;
25 import java.util.Random;
26 
27 import junit.framework.TestCase;
28 
29 import com.sleepycat.je.CacheMode;
30 import com.sleepycat.je.Cursor;
31 import com.sleepycat.je.Database;
32 import com.sleepycat.je.DatabaseException;
33 import com.sleepycat.je.DbInternal;
34 import com.sleepycat.je.DbTestProxy;
35 import com.sleepycat.je.Environment;
36 import com.sleepycat.je.EnvironmentConfig;
37 import com.sleepycat.je.ExceptionEvent;
38 import com.sleepycat.je.ExceptionListener;
39 import com.sleepycat.je.StatsConfig;
40 import com.sleepycat.je.dbi.CursorImpl;
41 import com.sleepycat.je.dbi.EnvironmentImpl;
42 import com.sleepycat.je.latch.LatchSupport;
43 import com.sleepycat.je.log.FileManager;
44 import com.sleepycat.je.tree.BIN;
45 import com.sleepycat.je.tree.ChildReference;
46 import com.sleepycat.je.tree.IN;
47 import com.sleepycat.je.tree.LN;
48 import com.sleepycat.je.tree.SearchResult;
49 import com.sleepycat.je.tree.Tree;
50 import com.sleepycat.je.tree.WithRootLatched;
51 import com.sleepycat.util.test.SharedTestUtils;
52 import com.sleepycat.utilint.StringUtils;
53 
54 public class TestUtils {
55     public static String DEST_DIR = SharedTestUtils.DEST_DIR;
56     public static String NO_SYNC = SharedTestUtils.NO_SYNC;
57 
58     public static final String LOG_FILE_NAME = "00000000.jdb";
59 
60     public static final StatsConfig FAST_STATS;
61 
62     static {
63         FAST_STATS = new StatsConfig();
64         FAST_STATS.setFast(true);
65     }
66 
67     private static final boolean DEBUG = true;
68     private static Random rnd = new Random();
69 
debugMsg(String message)70     public void debugMsg(String message) {
71 
72         if (DEBUG) {
73             System.out.println
74                 (Thread.currentThread().toString() + " " + message);
75         }
76     }
77 
setRandomSeed(int seed)78     static public void setRandomSeed(int seed) {
79 
80         rnd = new Random(seed);
81     }
82 
generateRandomAlphaBytes(byte[] bytes)83     static public void generateRandomAlphaBytes(byte[] bytes) {
84 
85         byte[] aAndZ = StringUtils.toUTF8("AZ");
86         int range = aAndZ[1] - aAndZ[0] + 1;
87 
88         for (int i = 0; i < bytes.length; i++) {
89             bytes[i] = (byte) (rnd.nextInt(range) + aAndZ[0]);
90         }
91     }
92 
checkLatchCount()93     static public void checkLatchCount() {
94         TestCase.assertTrue(LatchSupport.nBtreeLatchesHeld() == 0);
95     }
96 
printLatchCount(String msg)97     static public void printLatchCount(String msg) {
98         System.out.println(msg + " : " + LatchSupport.nBtreeLatchesHeld());
99     }
100 
printLatches(String msg)101     static public void printLatches(String msg) {
102         System.out.println(msg + " : ");
103         LatchSupport.dumpBtreeLatchesHeld();
104     }
105 
106     /**
107      * Generate a synthetic base 26 four byte alpha key from an int.
108      * The bytes of the key are between 'A' and 'Z', inclusive.  0 maps
109      * to 'AAAA', 1 to 'AAAB', etc.
110      */
alphaKey(int i)111     static public int alphaKey(int i) {
112 
113         int ret = 0;
114         for (int j = 0; j < 4; j++) {
115             byte b = (byte) (i % 26);
116             ret <<= 8;
117             ret |= (b + 65);
118             i /= 26;
119         }
120 
121         return ret;
122     }
123 
124     /**
125      * Marshall an unsigned int (long) into a four byte buffer.
126      */
putUnsignedInt(byte[] buf, long value)127     static public void putUnsignedInt(byte[] buf, long value) {
128 
129         int i = 0;
130         buf[i++] = (byte) (value >>> 0);
131         buf[i++] = (byte) (value >>> 8);
132         buf[i++] = (byte) (value >>> 16);
133         buf[i] =   (byte) (value >>> 24);
134     }
135 
136     /**
137      * All flavors of removeLogFiles should check if the remove has been
138      * disabled. (Used for debugging, so that the tester can dump the
139      * log file.
140      */
removeDisabled()141     private static boolean removeDisabled() {
142 
143         String doRemove = System.getProperty("removeLogFiles");
144         return ((doRemove != null) && doRemove.equalsIgnoreCase("false"));
145     }
146 
147     /**
148      * Remove je log files from the home directory. Will be disabled
149      * if the unit test is run with -DremoveLogFiles=false
150      * @param msg prefix to append to error messages
151      * @param envFile environment directory
152      */
removeLogFiles(String msg, File envFile, boolean checkRemove)153     public static void removeLogFiles(String msg,
154                                       File envFile,
155                                       boolean checkRemove) {
156         removeFiles(msg, envFile, FileManager.JE_SUFFIX, checkRemove);
157         removeSubDirs(envFile);
158     }
159 
160     /**
161      * Remove files with this suffix from the je home directory
162      * @param msg prefix to append to error messages
163      * @param envFile environment directory
164      * @param suffix files with this suffix will be removed
165      */
removeFiles(String msg, File envFile, String suffix)166     public static void removeFiles(String msg,
167                                    File envFile,
168                                    String suffix) {
169         removeFiles(msg, envFile, suffix, false);
170     }
171 
172     /**
173      * Remove files with this suffix from the je home directory
174      * @param msg prefix to append to error messages
175      * @param envFile environment directory
176      * @param suffix files with this suffix will be removed
177      * @param checkRemove if true, check the -DremoveLogFiles system
178      *  property before removing.
179      */
removeFiles(String msg, File envFile, String suffix, boolean checkRemove)180     public static void removeFiles(String msg,
181                                    File envFile,
182                                    String suffix,
183                                    boolean checkRemove) {
184         if (checkRemove && removeDisabled()) {
185             return;
186         }
187 
188         String[] suffixes = new String[] { suffix };
189         String[] names = FileManager.listFiles(envFile, suffixes, false);
190 
191         /* Clean up any target files in this directory. */
192         for (int i = 0; i < names.length; i++) {
193             File oldFile = new File(envFile, names[i]);
194             boolean done = oldFile.delete();
195             assert done :
196                 msg + " directory = " + envFile +
197                 " couldn't delete " + names[i] + " out of " +
198                 names[names.length - 1];
199             oldFile = null;
200         }
201     }
202 
203     /**
204      * Remove files with the pattern indicated by the filename filter from the
205      * environment home directory.
206      * Note that BadFileFilter looks for this pattern: NNNNNNNN.bad.#
207      *           InfoFileFilter looks for this pattern: je.info.#
208      * @param envFile environment directory
209      */
removeFiles(File envFile, FilenameFilter filter)210     public static void removeFiles(File envFile, FilenameFilter filter) {
211         if (removeDisabled()) {
212             return;
213         }
214 
215         File[] targetFiles = envFile.listFiles(filter);
216 
217         // Clean up any target files in this directory
218         for (int i = 0; i < targetFiles.length; i++) {
219             boolean done = targetFiles[i].delete();
220             if (!done) {
221                 System.out.println
222                     ("Warning, couldn't delete "
223                      + targetFiles[i]
224                      + " out of "
225                      + targetFiles[targetFiles.length - 1]);
226             }
227         }
228     }
229 
230     /**
231      * Useful utility for generating byte arrays with a known order.
232      * Vary the length just to introduce more variability.
233      * @return a byte array of length val % 100 with the value of "val"
234      */
getTestArray(int val)235     public static byte[] getTestArray(int val) {
236 
237         int length = val % 10;
238         length = length < 4 ? 4 : length;
239         byte[] test = new byte[length];
240         test[3] = (byte) ((val >>> 0) & 0xff);
241         test[2] = (byte) ((val >>> 8) & 0xff);
242         test[1] = (byte) ((val >>> 16) & 0xff);
243         test[0] = (byte) ((val >>> 24) & 0xff);
244         return test;
245     }
246 
247     /**
248      * Return the value of a test data array generated with getTestArray
249      * as an int
250      */
getTestVal(byte[] testArray)251     public static int getTestVal(byte[] testArray) {
252 
253         int val = 0;
254         val |= (testArray[3] & 0xff);
255         val |= ((testArray[2] & 0xff) << 8);
256         val |= ((testArray[1] & 0xff) << 16);
257         val |= ((testArray[0] & 0xff) << 24);
258         return val;
259     }
260 
261     /**
262      * @return length and data of a byte array, printed as decimal numbers
263      */
dumpByteArray(byte[] b)264     public static String dumpByteArray(byte[] b) {
265 
266         StringBuilder sb = new StringBuilder();
267         sb.append("<byteArray len = ");
268         sb.append(b.length);
269         sb.append(" data = \"");
270         for (int i = 0; i < b.length; i++) {
271             sb.append(b[i]).append(",");
272         }
273         sb.append("\"/>");
274         return sb.toString();
275     }
276 
277     /**
278      * @return a copy of the passed in byte array
279      */
byteArrayCopy(byte[] ba)280     public static byte[] byteArrayCopy(byte[] ba) {
281 
282         int len = ba.length;
283         byte[] ret = new byte[len];
284         System.arraycopy(ba, 0, ret, 0, len);
285         return ret;
286     }
287 
288     /*
289      * Check that the stored memory count for all INs on the inlist
290      * matches their computed count. The environment mem usage check
291      * may be run with assertions or not.
292      *
293      * In a multithreaded environment (or one with daemons running),
294      * you can't be sure that the cached size will equal the calculated size.
295      *
296      * Nodes, txns, and locks are all counted within the memory budget.
297      */
validateNodeMemUsage(EnvironmentImpl envImpl, boolean assertOnError)298     public static long validateNodeMemUsage(EnvironmentImpl envImpl,
299                                             boolean assertOnError)
300         throws DatabaseException {
301 
302         TreeMemTally tally = tallyTreeMemUsage(envImpl);
303         long nodeTallyUsage = tally.treeNodeUsage;
304         long nodeCacheUsage = envImpl.getMemoryBudget().getTreeMemoryUsage();
305         NumberFormat formatter = NumberFormat.getNumberInstance();
306         if (assertOnError) {
307             assert (nodeTallyUsage == nodeCacheUsage) :
308                   "treeNodeTallyUsage=" + formatter.format(nodeTallyUsage) +
309                   " treeNodeCacheUsage=" + formatter.format(nodeCacheUsage);
310         } else {
311             if (DEBUG) {
312                 if (nodeCacheUsage != nodeTallyUsage) {
313                     double diff = Math.abs(nodeCacheUsage - nodeTallyUsage);
314                     if ((diff / nodeCacheUsage) > .05) {
315                         System.out.println("treeNodeTallyUsage=" +
316                                            formatter.format(nodeTallyUsage) +
317                                            " treeNodeCacheUsage=" +
318                                            formatter.format(nodeCacheUsage));
319                     }
320                 }
321             }
322         }
323 
324         long adminTallyUsage = tally.treeAdminUsage;
325         long adminCacheUsage =
326             envImpl.getMemoryBudget().getTreeAdminMemoryUsage();
327         if (assertOnError) {
328             assert (adminTallyUsage == adminCacheUsage) :
329                   "treeAdminTallyUsage=" + formatter.format(adminTallyUsage) +
330                   " treeAdminCacheUsage=" + formatter.format(adminCacheUsage);
331         } else {
332             if (DEBUG) {
333                 if (adminCacheUsage != adminTallyUsage) {
334                     double diff = Math.abs(adminCacheUsage - adminTallyUsage);
335                     if ((diff / adminCacheUsage) > .05) {
336                         System.out.println("treeAdminTallyUsage=" +
337                                            formatter.format(adminTallyUsage) +
338                                            " treeAdminCacheUsage=" +
339                                            formatter.format(adminCacheUsage));
340                     }
341                 }
342             }
343         }
344 
345         return nodeCacheUsage;
346     }
347 
tallyNodeMemUsage(EnvironmentImpl envImpl)348     public static long tallyNodeMemUsage(EnvironmentImpl envImpl)
349         throws DatabaseException {
350 
351         return tallyTreeMemUsage(envImpl).treeNodeUsage;
352     }
353 
354     static class TreeMemTally {
355         final long treeNodeUsage;
356         final long treeAdminUsage;
357 
TreeMemTally(long treeNodeUsage, long treeAdminUsage)358         TreeMemTally(long treeNodeUsage, long treeAdminUsage) {
359             this.treeNodeUsage = treeNodeUsage;
360             this.treeAdminUsage = treeAdminUsage;
361         }
362     }
363 
tallyTreeMemUsage(EnvironmentImpl envImpl)364     private static TreeMemTally tallyTreeMemUsage(EnvironmentImpl envImpl)
365         throws DatabaseException {
366 
367         long treeNodeUsage = 0;
368         long treeAdminUsage = envImpl.getDbTree().getTreeAdminMemory();
369         for (IN in : envImpl.getInMemoryINs()) {
370             in.latch();
371             try {
372                 assert in.verifyMemorySize():
373                     "in nodeId=" + in.getNodeId() +
374                     ' ' + in.getClass().getName();
375 
376                 treeNodeUsage += in.getBudgetedMemorySize();
377 
378                 for (int i = 0; i < in.getNEntries(); i += 1) {
379                     Object child = in.getTarget(i);
380                     if (child instanceof LN) {
381                         treeAdminUsage += ((LN) child).getTreeAdminMemory();
382                     }
383                 }
384             } finally {
385                 in.releaseLatch();
386             }
387         }
388         return new TreeMemTally(treeNodeUsage, treeAdminUsage);
389     }
390 
391     /**
392      * Called by each unit test to enforce isolation level settings specified
393      * in the isolationLevel system property.  Other system properties or
394      * default settings may be applied in the future.
395      */
initEnvConfig()396     public static EnvironmentConfig initEnvConfig() {
397 
398         EnvironmentConfig config = new EnvironmentConfig();
399         String val = System.getProperty("isolationLevel");
400         if (val != null && val.length() > 0) {
401             if ("serializable".equals(val)) {
402                 config.setTxnSerializableIsolation(true);
403             } else if ("readCommitted".equals(val)) {
404                 DbInternal.setTxnReadCommitted(config, true);
405             } else {
406                 throw new IllegalArgumentException
407                     ("Unknown isolationLevel system property value: " + val);
408             }
409         }
410         return config;
411     }
412 
413     /**
414      * If a unit test needs to override the isolation level, it should call
415      * this method after calling initEnvConfig.
416      */
clearIsolationLevel(EnvironmentConfig config)417     public static void clearIsolationLevel(EnvironmentConfig config) {
418         DbInternal.setTxnReadCommitted(config, false);
419         config.setTxnSerializableIsolation(false);
420     }
421 
422     /**
423      * Loads the given resource relative to the given class, and copies it to
424      * log file zero in the given directory.
425      */
loadLog(Class<?> cls, String resourceName, File envHome)426     public static void loadLog(Class<?> cls, String resourceName, File envHome)
427         throws IOException {
428 
429         loadLog(cls, resourceName, envHome, LOG_FILE_NAME);
430     }
431 
432     /**
433      * Loads the given resource relative to the given class, and copies it to
434      * the given log file in the given directory.
435      */
loadLog(Class cls, String resourceName, File envHome, String logFileName)436     public static void loadLog(Class cls,
437                                String resourceName,
438                                File envHome,
439                                String logFileName)
440         throws IOException {
441 
442         File logFile = new File(envHome, logFileName);
443         InputStream is = cls.getResourceAsStream(resourceName);
444         OutputStream os = new FileOutputStream(logFile);
445         byte[] buf = new byte[is.available()];
446         int len = is.read(buf);
447         if (buf.length != len) {
448             throw new IllegalStateException();
449         }
450         os.write(buf, 0, len);
451         is.close();
452         os.close();
453     }
454 
455     /**
456      * Logs the BIN at the cursor provisionally and the parent IN
457      * non-provisionally.  Used to simulate a partial checkpoint or eviction.
458      */
logBINAndIN(Environment env, Cursor cursor)459     public static void logBINAndIN(Environment env, Cursor cursor)
460         throws DatabaseException {
461 
462         logBINAndIN(env, cursor, false /*allowDeltas*/);
463     }
464 
logBINAndIN(Environment env, Cursor cursor, boolean allowDeltas)465     public static void logBINAndIN(Environment env,
466                                    Cursor cursor,
467                                    boolean allowDeltas)
468         throws DatabaseException {
469 
470         BIN bin = getBIN(cursor);
471         Tree tree = bin.getDatabase().getTree();
472 
473         /* Log the BIN and update its parent entry. */
474         bin.latch();
475 
476         SearchResult result = tree.getParentINForChildIN(
477             bin, false, /*useTargetLevel*/
478             true, CacheMode.DEFAULT);
479 
480         assert result.parent != null;
481         assert result.exactParentFound;
482         IN binParent = result.parent;
483 
484         long binLsn = logIN(env, bin, allowDeltas, true, binParent);
485 
486         binParent.updateNode(result.index, bin, binLsn, 0 /*lastLoggedSize*/);
487 
488         result.parent.releaseLatch();
489 
490         /* Log the BIN parent and update its parent entry. */
491         binParent.latch();
492 
493         if (binParent.isRoot()) {
494             binParent.releaseLatch();
495             result.parent = null;
496         } else {
497             result = tree.getParentINForChildIN(
498                 binParent, false, /*useTargetLevel*/
499                 true, CacheMode.DEFAULT);
500         }
501 
502         IN inParent = null;
503         if (result.parent != null) {
504             result.parent.releaseLatch();
505             assert result.exactParentFound;
506             inParent = result.parent;
507             inParent.latch();
508         }
509 
510         final long inLsn = logIN(env, binParent, allowDeltas, false, null);
511 
512         if (inParent != null) {
513             inParent.updateNode(
514                 result.index, binParent, inLsn, 0 /*lastLoggedSize*/);
515 
516             inParent.releaseLatch();
517         } else {
518             tree.withRootLatchedExclusive(new WithRootLatched() {
519                 public IN doWork(ChildReference root) {
520                     root.setLsn(inLsn);
521                     return null;
522                 }
523             });
524         }
525     }
526 
527     /**
528      * Logs the given IN.
529      */
logIN(Environment env, IN in, boolean allowDeltas, boolean provisional, IN parent)530     public static long logIN(Environment env,
531                              IN in,
532                              boolean allowDeltas,
533                              boolean provisional,
534                              IN parent)
535         throws DatabaseException {
536 
537         EnvironmentImpl envImpl = DbInternal.getEnvironmentImpl(env);
538         in.latch();
539         long lsn = in.log(envImpl.getLogManager(), allowDeltas,
540                           false /*allowCompress*/, provisional,
541                           false /*backgroundIO*/, parent);
542         in.releaseLatch();
543         return lsn;
544     }
545 
546     /**
547      * Returns the parent IN of the given IN.
548      */
getIN(IN in)549     public static IN getIN(IN in)
550         throws DatabaseException {
551 
552         Tree tree = in.getDatabase().getTree();
553         in.latch();
554 
555         SearchResult result = tree.getParentINForChildIN(
556             in, false, /*useTargetLevel*/
557             true, CacheMode.DEFAULT);
558 
559         assert result.parent != null;
560         result.parent.releaseLatch();
561         assert result.exactParentFound;
562         return result.parent;
563     }
564 
565     /**
566      * Returns the target BIN for the given cursor.
567      */
getBIN(Cursor cursor)568     public static BIN getBIN(Cursor cursor) {
569         CursorImpl impl = DbTestProxy.dbcGetCursorImpl(cursor);
570         BIN bin = impl.getBIN();
571         assert bin != null;
572         return bin;
573     }
574 
575     /**
576      * Assert if the tree is not this deep. Use to ensure that data setups
577      * are as expected.
578      */
checkTreeDepth(Database db, int desiredDepth)579     public static boolean checkTreeDepth(Database db, int desiredDepth)
580         throws DatabaseException {
581 
582         Tree tree = DbInternal.getDatabaseImpl(db).getTree();
583         IN rootIN = tree.getRootIN(CacheMode.UNCHANGED);
584         int level = 0;
585         if (rootIN != null) {
586             level = rootIN.getLevel() & IN.LEVEL_MASK;
587             rootIN.releaseLatch();
588         }
589 
590         return (desiredDepth == level);
591     }
592 
593     /**
594      * @return true if long running tests are enabled.
595      */
runLongTests()596     static public boolean runLongTests() {
597         return SharedTestUtils.runLongTests();
598     }
599 
600     /**
601      * Skip over the JE version number at the start of the exception
602      * message for tests which are looking for a specific message.
603      */
skipVersion(Exception e)604     public static String skipVersion(Exception e) {
605         final String header = DatabaseException.getVersionHeader();
606         final String msg = e.getMessage();
607         if (msg == null || !msg.startsWith(header)) {
608             return msg;
609         }
610         return msg.substring(header.length());
611     }
612 
createEnvHomeWithSubDir(File envHome, int subDirNumber)613     public static void createEnvHomeWithSubDir(File envHome,
614                                                int subDirNumber) {
615         if (!envHome.exists()) {
616             throw new IllegalStateException
617                 ("Environment home directory doesn't exist.");
618         }
619 
620         for (int i = 1; i <= subDirNumber; i++) {
621             String fileName = getSubDirName(i);
622             File subDir = new File(envHome, fileName);
623             subDir.mkdir();
624         }
625     }
626 
getSubDirName(int i)627     public static String getSubDirName(int i) {
628         if (i < 10) {
629             return "data00" + i;
630         } else if (i < 100) {
631             return "data0" + i;
632         } else if (i <= 256) {
633             return "data" + i;
634         } else {
635             throw new IllegalArgumentException
636                 ("The number of sub directories is invalid.");
637         }
638     }
639 
removeSubDirs(File envHome)640     public static void removeSubDirs(File envHome) {
641         if (envHome == null || !envHome.exists()) {
642             return;
643         }
644 
645         File[] files = envHome.listFiles();
646         for (File file : files) {
647             if (file.isDirectory() && file.getName().startsWith("data")) {
648                 File[] subFiles = file.listFiles();
649                 for (File subFile : subFiles) {
650                     subFile.delete();
651                 }
652                 file.delete();
653             }
654         }
655     }
656 
657     /* Read the je.properties and write a new configuration. */
readWriteJEProperties(File envHome, String configure)658     public static ArrayList<String> readWriteJEProperties(File envHome,
659                                                           String configure)
660         throws IOException {
661 
662         /* Read the je.properties. */
663         File propertyFile = new File(envHome, "je.properties");
664         BufferedReader reader =
665             new BufferedReader(new FileReader(propertyFile));
666         ArrayList<String> formerLines = new ArrayList<String>();
667         String line = null;
668         while ((line = reader.readLine()) != null) {
669             formerLines.add(line);
670         }
671         reader.close();
672 
673         /* Write the replicated parameters in the je.properties file. */
674         FileWriter writer = new FileWriter(propertyFile, true);
675         writer.append(configure + "\n");
676         writer.flush();
677         writer.close();
678 
679         return formerLines;
680     }
681 
682     /*
683      * Rewrite the je.properties with configurations, it will delete the old
684      * file and rewrite a new one.
685      */
reWriteJEProperties(File envHome, ArrayList<String> formerLines)686     public static void reWriteJEProperties(File envHome,
687                                            ArrayList<String> formerLines)
688         throws IOException {
689 
690         File propertyFile = new File(envHome, "je.properties");
691         /* Write the je.properties file with the former content. */
692         if (propertyFile.exists() && propertyFile.isFile()) {
693             TestCase.assertTrue(propertyFile.delete());
694         }
695         TestCase.assertTrue(!propertyFile.exists());
696 
697         propertyFile = new File(envHome, "je.properties");
698         TestCase.assertTrue(propertyFile.createNewFile());
699 
700         FileWriter writer = new FileWriter(propertyFile, true);
701         for (String configure : formerLines) {
702             writer.append(configure + "\n");
703         }
704         writer.flush();
705         writer.close();
706     }
707 
708     /* Serialize an object and read it again. */
serializeAndReadObject(File envHome, Object object)709     public static Object serializeAndReadObject(File envHome, Object object)
710         throws Exception {
711 
712         File output = new File(envHome, "configure.out");
713         ObjectOutputStream out =
714             new ObjectOutputStream(new FileOutputStream(output));
715         out.writeObject(object);
716         out.close();
717 
718         if (!output.exists()) {
719             throw new IllegalStateException
720                 ("Can't create the output for serialized object.");
721         }
722 
723         ObjectInputStream in =
724             new ObjectInputStream(new FileInputStream(output));
725         Object newObject = in.readObject();
726         in.close();
727 
728         if (!output.delete()) {
729             throw new IllegalStateException
730                 ("Can't delete the output for serialized object after " +
731                  "testing is done.");
732         }
733 
734         return newObject;
735     }
736 
737     /**
738      * Dump any exception messages to stderr.
739      */
740     public static class StdErrExceptionListener
741         implements ExceptionListener {
742 
exceptionThrown(ExceptionEvent event)743         public void exceptionThrown(ExceptionEvent event) {
744             System.err.println(Thread.currentThread() +
745                                " received " +
746                                event);
747         }
748     }
749 
750     /**
751      * Calls Closeable.close for each parameter in the order given, if it is
752      * non-null.
753      *
754      * If one or more close methods throws an Exception, all close methods will
755      * still be called and the first Exception will be rethrown.  If an Error
756      * is thrown by a close method, it will be thrown by this method and no
757      * further close methods will be called.  An IOException may be thrown by a
758      * close method because is declared by Closeable.close; however, the use of
759      * RuntimeExceptions is recommended.
760      */
closeAll(Closeable... objects)761     public static void closeAll(Closeable... objects)
762         throws Exception {
763 
764         closeAll(null, objects);
765     }
766 
767     /**
768      * Same as closeAll(Closeable...) but allows passing an initial exception,
769      * when one may have been thrown earlier during a shutdown procedure.  If
770      * null is passed for the firstEx parameter, calling this method is
771      * equivalent to calling closeAll(Closeable...).
772      */
closeAll(Exception firstEx, Closeable... objects)773     public static void closeAll(Exception firstEx, Closeable... objects)
774         throws Exception {
775 
776         for (Closeable c : objects) {
777             if (c == null) {
778                 continue;
779             }
780             try {
781                 c.close();
782             } catch (Exception e) {
783                 if (firstEx == null) {
784                     firstEx = e;
785                 }
786             }
787         }
788 
789         if (firstEx != null) {
790             throw firstEx;
791         }
792     }
793 }
794