3 /* Copyright: Thomas McGlynn 1999.
4 * This code may be used for any purpose, non-commercial
5 * or commercial so long as this copyright notice is retained
6 * in the source code or included in or referred to in any
9 import nom.tam.util.RandomAccess;
11 import java.io.IOException;
13 import java.io.FileInputStream;
14 import java.io.FilterInputStream;
15 import java.io.InputStream;
16 import java.io.OutputStream;
17 import java.io.PushbackInputStream;
18 import java.io.UnsupportedEncodingException;
19 import java.lang.reflect.Constructor;
22 import java.net.URLConnection;
25 import java.util.List;
26 import java.util.zip.GZIPInputStream;
27 import nom.tam.util.ArrayDataOutput;
28 import nom.tam.util.AsciiFuncs;
30 /** This class comprises static
31 * utility functions used throughout
34 public class FitsUtil {
36 private static boolean wroteCheckingError = false;
38 /** Reposition a random access stream to a requested offset */
39 public static void reposition(Object o, long offset)
40 throws FitsException {
43 throw new FitsException("Attempt to reposition null stream");
45 if (!(o instanceof RandomAccess)
47 throw new FitsException("Invalid attempt to reposition stream " + o
48 + " of type " + o.getClass().getName()
53 ((RandomAccess) o).seek(offset);
54 } catch (IOException e) {
55 throw new FitsException("Unable to repostion stream " + o
56 + " of type " + o.getClass().getName()
57 + " to " + offset + " Exception:" + e);
61 /** Find out where we are in a random access file */
62 public static long findOffset(Object o) {
64 if (o instanceof RandomAccess) {
65 return ((RandomAccess) o).getFilePointer();
71 /** How many bytes are needed to fill the last 2880 block? */
72 public static int padding(int size) {
73 return padding((long) size);
76 public static int padding(long size) {
78 int mod = (int) (size % 2880);
85 /** Total size of blocked FITS element */
86 public static int addPadding(int size) {
87 return size + padding(size);
90 public static long addPadding(long size) {
91 return size + padding(size);
94 /** This method decompresses a compressed
95 * input stream. The decompression method is
96 * selected automatically based upon the first two bytes read.
97 * @param compressed The compressed input stram
98 * @return A stream which wraps the input stream and decompresses
99 * it. If the input stream is not compressed, a
100 * pushback input stream wrapping the original stream is returned.
102 static InputStream decompress(InputStream compressed) throws FitsException {
104 PushbackInputStream pb = new PushbackInputStream(compressed, 2);
113 if (mag1 == 0x1f && mag2 == 0x8b) {
114 // Push the data back into the stream
117 return new GZIPInputStream(pb);
118 } else if (mag1 == 0x1f && mag2 == 0x9d) {
119 // Push the data back into the stream
122 return compressInputStream(pb);
123 } else if (mag1 == 'B' && mag2 == 'Z') {
124 if (System.getenv("BZIP_DECOMPRESSOR") != null) {
127 return bunzipper(pb);
130 String cname = "org.apache.tools.bzip2.CBZip2InputStream";
131 // Note that we forego generics here since we don't
132 // want any explicit mention of this class so that users
133 // can compile and run without worrying about having the class in hand.
135 Constructor con = Class.forName(cname).getConstructor(InputStream.class);
136 return (InputStream) con.newInstance(pb);
137 } catch (Exception e) {
138 System.err.println("Unable to find constructor for BZIP2 decompression. Is the Apache BZIP jar in the classpath?");
139 throw new FitsException("No CBZip2InputStream class found for bzip2 compressed file");
142 // Push the data back into the stream
148 } catch (IOException e) {
149 // This is probably a prelude to failure...
150 throw new FitsException("Unable to analyze input stream");
154 static InputStream compressInputStream(final InputStream compressed) throws FitsException {
156 Process proc = new ProcessBuilder("uncompress", "-c").start();
158 // This is the input to the process -- but
159 // an output from here.
160 final OutputStream input = proc.getOutputStream();
162 // Now copy everything in a separate thread.
163 Thread copier = new Thread(
168 byte[] buffer = new byte[8192];
170 while ((len = compressed.read(buffer, 0, buffer.length)) > 0) {
171 input.write(buffer, 0, len);
175 } catch (IOException e) {
181 return proc.getInputStream();
182 } catch (Exception e) {
183 throw new FitsException("Unable to read .Z compressed stream.\nIs `uncompress' in the path?\n:" + e);
187 /** Is a file compressed? */
188 public static boolean isCompressed(File test) {
189 InputStream fis = null;
192 fis = new FileInputStream(test);
193 int mag1 = fis.read();
194 int mag2 = fis.read();
196 if (mag1 == 0x1f && (mag2 == 0x8b || mag2 == 0x9d)) {
198 } else if (mag1 == 'B' && mag2 == 'Z') {
205 } catch (IOException e) {
206 // This is probably a prelude to failure...
213 } catch (IOException e) {
220 /** Check if a file seems to be compressed.
222 public static boolean isCompressed(String filename) {
223 if (filename == null) {
226 FileInputStream fis = null;
227 File test = new File(filename);
229 return isCompressed(test);
232 int len = filename.length();
233 return len > 2 && (filename.substring(len - 3).equalsIgnoreCase(".gz") || filename.substring(len - 2).equals(".Z"));
236 /** Get the maximum length of a String in a String array.
238 public static int maxLength(String[] o) throws FitsException {
241 for (int i = 0; i < o.length; i += 1) {
242 if (o[i] != null && o[i].length() > max) {
249 /** Copy an array of Strings to bytes.*/
250 public static byte[] stringsToByteArray(String[] o, int maxLen) {
251 byte[] res = new byte[o.length * maxLen];
252 for (int i = 0; i < o.length; i += 1) {
257 bstr = AsciiFuncs.getBytes(o[i]);
259 int cnt = bstr.length;
263 System.arraycopy(bstr, 0, res, i * maxLen, cnt);
264 for (int j = cnt; j < maxLen; j += 1) {
265 res[i * maxLen + j] = (byte) ' ';
271 /** Convert bytes to Strings */
272 public static String[] byteArrayToStrings(byte[] o, int maxLen) {
273 boolean checking = FitsFactory.getCheckAsciiStrings();
275 // Note that if a String in a binary table contains an internal 0,
276 // the FITS standard says that it is to be considered as terminating
277 // the string at that point, so that software reading the
278 // data back may not include subsequent characters.
279 // No warning of this truncation is given.
281 String[] res = new String[o.length / maxLen];
282 for (int i = 0; i < res.length; i += 1) {
284 int start = i * maxLen;
285 int end = start + maxLen;
286 // Pre-trim the string to avoid keeping memory
287 // hanging around. (Suggested by J.C. Segovia, ESA).
289 // Note that the FITS standard does not mandate
290 // that we should be trimming the string at all, but
291 // this seems to best meet the desires of the community.
292 for (; start < end; start += 1) {
293 if (o[start] != 32) {
294 break; // Skip only spaces.
298 for (; end > start; end -= 1) {
299 if (o[end - 1] != 32) {
304 // For FITS binary tables, 0 values are supposed
305 // to terminate strings, a la C. [They shouldn't appear in
306 // any other context.]
307 // Other non-printing ASCII characters
308 // should always be an error which we can check for
309 // if the user requests.
311 // The lack of handling of null bytes was noted by Laurent Bourges.
312 boolean errFound = false;
313 for (int j = start; j < end; j += 1) {
320 if (o[j] < 32 || o[j] > 126) {
326 res[i] = AsciiFuncs.asciiString(o, start, end - start);
327 if (errFound && !wroteCheckingError) {
328 System.err.println("Warning: Invalid ASCII character[s] detected in string:" + res[i]);
329 System.err.println(" Converted to space[s]. Any subsequent invalid characters will be converted silently");
330 wroteCheckingError = true;
337 /** Convert an array of booleans to bytes */
338 static byte[] booleanToByte(boolean[] bool) {
340 byte[] byt = new byte[bool.length];
341 for (int i = 0; i < bool.length; i += 1) {
342 byt[i] = bool[i] ? (byte) 'T' : (byte) 'F';
347 /** Convert an array of bytes to booleans */
348 static boolean[] byteToBoolean(byte[] byt) {
349 boolean[] bool = new boolean[byt.length];
354 for (int i = 0; i < byt.length; i += 1) {
355 bool[i] = (byt[i] == 'T');
360 /** Get a stream to a URL accommodating possible redirections.
361 * Note that if a redirection request points to a different
362 * protocol than the original request, then the redirection
363 * is not handled automatically.
365 public static InputStream getURLStream(URL url, int level) throws IOException {
367 // Hard coded....sigh
369 throw new IOException("Two many levels of redirection in URL");
371 URLConnection conn = url.openConnection();
372 // Map<String,List<String>> hdrs = conn.getHeaderFields();
373 Map hdrs = conn.getHeaderFields();
375 // Read through the headers and see if there is a redirection header.
376 // We loop (rather than just do a get on hdrs)
377 // since we want to match without regard to case.
378 String[] keys = (String[]) hdrs.keySet().toArray(new String[0]);
379 // for (String key: hdrs.keySet()) {
380 for (int i = 0; i < keys.length; i += 1) {
381 String key = keys[i];
383 if (key != null && key.toLowerCase().equals("location")) {
384 // String val = hdrs.get(key).get(0);
385 String val = (String) ((List) hdrs.get(key)).get(0);
388 if (val.length() > 0) {
390 return getURLStream(new URL(val), level + 1);
396 return conn.getInputStream();
399 /** Add padding to an output stream. */
400 public static void pad(ArrayDataOutput stream, long size) throws FitsException {
401 pad(stream, size, (byte) 0);
404 /** Add padding to an output stream. */
405 public static void pad(ArrayDataOutput stream, long size, byte fill)
406 throws FitsException {
407 int len = padding(size);
409 byte[] buf = new byte[len];
410 for (int i = 0; i < len; i += 1) {
416 } catch (Exception e) {
417 throw new FitsException("Unable to write padding", e);
422 static InputStream bunzipper(final InputStream pb) throws FitsException {
423 String cmd = System.getenv("BZIP_DECOMPRESSOR");
424 // Allow the user to have already specified the - option.
425 if (cmd.indexOf(" -") < 0) {
428 final OutputStream out;
429 String[] flds = cmd.split(" +");
433 p = new ProcessBuilder(flds).start();
434 out = p.getOutputStream();
436 t = new Thread(new Runnable() {
440 byte[] buf = new byte[16384];
443 while ((len = pb.read(buf)) > 0) {
445 out.write(buf, 0, len);
446 } catch (Exception e) {
447 // Skip this. It can happen when we
448 // stop reading the compressed file in mid stream.
456 } catch (IOException e) {
457 throw new Error("Error reading BZIP compression using: " + System.getenv("BZIP_DECOMPRESSOR"), e);
462 } catch (Exception e) {
463 throw new FitsException("Error initiating BZIP decompression: " + e);
466 return new CloseIS(p.getInputStream(), pb, out);
471 class CloseIS extends FilterInputStream {
476 CloseIS(InputStream inp, InputStream i, OutputStream o) {
482 public void close() throws IOException {