Java tutorial
/* * Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002. * All rights reserved. Software written by Ian F. Darwin and others. * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Java, the Duke mascot, and all variants of Sun's Java "steaming coffee * cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's, * pioneering role in inventing and promulgating (and standardizing) the Java * language and environment is gratefully acknowledged. * * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for * inventing predecessor languages C and C++ is also gratefully acknowledged. */ /* Error handling throughout. In particular, the reading code needs to checksum! Write getInputStream(). Here's how: seek on the file, malloc a buffer big enough to hold the file, read it into memory, return a ByteArrayInputStream. Handle LF_LINK linked files: getEntry(name).getOffset(). Think about writing Tar files? */ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.Vector; /** * Demonstrate the Tar archive lister. * * @author Ian F. Darwin, http://www.darwinsys.com/ * @version $Id: TarList.java,v 1.7 2004/03/07 17:47:35 ian Exp $ */ public class TarList { public static void main(String[] argv) throws IOException, TarException { if (argv.length == 0) { System.err.println("Usage: TarList archive"); System.exit(1); } new TarList(argv[0]).list(); } /** The TarFile we are reading */ TarFile tf; /** Constructor */ public TarList(String fileName) { tf = new TarFile(fileName); } /** Generate and print the listing */ public void list() throws IOException, TarException { Enumeration list = tf.entries(); while (list.hasMoreElements()) { TarEntry e = (TarEntry) list.nextElement(); System.out.println(toListFormat(e)); } } protected StringBuffer sb; /** Shift used in formatting permissions */ protected static int[] shft = { 6, 3, 0 }; /** Format strings used in permissions */ protected static String rwx[] = { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx" }; /** NumberFormat used in formatting List form string */ NumberFormat sizeForm = new DecimalFormat("00000000"); /** Date used in printing mtime */ Date date = new Date(); SimpleDateFormat dateForm = new SimpleDateFormat("yyyy-MM-dd HH:mm"); /** Format a TarEntry the same way that UNIX tar does */ public String toListFormat(TarEntry e) { sb = new StringBuffer(); switch (e.type) { case TarEntry.LF_OLDNORMAL: case TarEntry.LF_NORMAL: case TarEntry.LF_CONTIG: case TarEntry.LF_LINK: // hard link: same as file sb.append('-'); // 'f' would be sensible break; case TarEntry.LF_DIR: sb.append('d'); break; case TarEntry.LF_SYMLINK: sb.append('l'); break; case TarEntry.LF_CHR: // UNIX character device file sb.append('c'); break; case TarEntry.LF_BLK: // UNIX block device file sb.append('b'); break; case TarEntry.LF_FIFO: // UNIX named pipe sb.append('p'); break; default: // Can't happen? sb.append('?'); break; } // Convert e.g., 754 to rwxrw-r-- int mode = e.getMode(); for (int i = 0; i < 3; i++) { sb.append(rwx[mode >> shft[i] & 007]); } sb.append(' '); // owner and group sb.append(e.getUname()).append('/').append(e.getGname()).append(' '); // size // DecimalFormat can't do "%-9d", so we do part of it ourselves sb.append(' '); String t = sizeForm.format(e.getSize()); boolean digit = false; char c; for (int i = 0; i < 8; i++) { c = t.charAt(i); if (!digit && i < (8 - 1) && c == '0') sb.append(' '); // leading space else { digit = true; sb.append(c); } } sb.append(' '); // mtime // copy file's mtime into Data object (after scaling // from "sec since 1970" to "msec since 1970"), and format it. date.setTime(1000 * e.getTime()); sb.append(dateForm.format(date)).append(' '); sb.append(e.getName()); if (e.isLink()) sb.append(" link to ").append(e.getLinkName()); if (e.isSymLink()) sb.append(" -> ").append(e.getLinkName()); return sb.toString(); } } /** * One entry in an archive file. * * @author Ian Darwin * @version $Id: TarEntry.java,v 1.7 2004/03/06 21:16:19 ian Exp $ * @note Tar format info taken from John Gilmore's public domain tar program, * @(#)tar.h 1.21 87/05/01 Public Domain, which said: "Created 25 August 1985 by * John Gilmore, ihnp4!hoptoad!gnu." John is now gnu@toad.com, and by * another path tar.h is GPL'd in GNU Tar. */ class TarEntry { /** Where in the tar archive this entry's HEADER is found. */ public long fileOffset = 0; /** The maximum size of a name */ public static final int NAMSIZ = 100; public static final int TUNMLEN = 32; public static final int TGNMLEN = 32; // Next fourteen fields constitute one physical record. // Padded to TarFile.RECORDSIZE bytes on tape/disk. // Lazy Evaluation: just read fields in raw form, only format when asked. /** File name */ byte[] name = new byte[NAMSIZ]; /** permissions, e.g., rwxr-xr-x? */ byte[] mode = new byte[8]; /* user */ byte[] uid = new byte[8]; /* group */ byte[] gid = new byte[8]; /* size */ byte[] size = new byte[12]; /* UNIX modification time */ byte[] mtime = new byte[12]; /* checksum field */ byte[] chksum = new byte[8]; byte type; byte[] linkName = new byte[NAMSIZ]; byte[] magic = new byte[8]; byte[] uname = new byte[TUNMLEN]; byte[] gname = new byte[TGNMLEN]; byte[] devmajor = new byte[8]; byte[] devminor = new byte[8]; // End of the physical data fields. /* The magic field is filled with this if uname and gname are valid. */ public static final byte TMAGIC[] = { // 'u', 's', 't', 'a', 'r', ' ', ' ', '\0' 0, 0, 0, 0, 0, 0, 0x20, 0x20, 0 }; /* 7 chars and a null */ /* Type value for Normal file, Unix compatibility */ public static final int LF_OLDNORMAL = '\0'; /* Type value for Normal file */ public static final int LF_NORMAL = '0'; /* Type value for Link to previously dumped file */ public static final int LF_LINK = '1'; /* Type value for Symbolic link */ public static final int LF_SYMLINK = '2'; /* Type value for Character special file */ public static final int LF_CHR = '3'; /* Type value for Block special file */ public static final int LF_BLK = '4'; /* Type value for Directory */ public static final int LF_DIR = '5'; /* Type value for FIFO special file */ public static final int LF_FIFO = '6'; /* Type value for Contiguous file */ public static final int LF_CONTIG = '7'; /* Constructor that reads the entry's header. */ public TarEntry(RandomAccessFile is) throws IOException, TarException { fileOffset = is.getFilePointer(); // read() returns -1 at EOF if (is.read(name) < 0) throw new EOFException(); // Tar pads to block boundary with nulls. if (name[0] == '\0') throw new EOFException(); // OK, read remaining fields. is.read(mode); is.read(uid); is.read(gid); is.read(size); is.read(mtime); is.read(chksum); type = is.readByte(); is.read(linkName); is.read(magic); is.read(uname); is.read(gname); is.read(devmajor); is.read(devminor); // Since the tar header is < 512, we need to skip it. is.skipBytes((int) (TarFile.RECORDSIZE - (is.getFilePointer() % TarFile.RECORDSIZE))); // TODO if checksum() fails, // throw new TarException("Failed to find next header"); } /** Returns the name of the file this entry represents. */ public String getName() { return new String(name).trim(); } public String getTypeName() { switch (type) { case LF_OLDNORMAL: case LF_NORMAL: return "file"; case LF_LINK: return "link w/in archive"; case LF_SYMLINK: return "symlink"; case LF_CHR: case LF_BLK: case LF_FIFO: return "special file"; case LF_DIR: return "directory"; case LF_CONTIG: return "contig"; default: throw new IllegalStateException("TarEntry.getTypeName: type " + type + " invalid"); } } /** Returns the UNIX-specific "mode" (type+permissions) of the entry */ public int getMode() { try { return Integer.parseInt(new String(mode).trim(), 8) & 0777; } catch (IllegalArgumentException e) { return 0; } } /** Returns the size of the entry */ public int getSize() { try { return Integer.parseInt(new String(size).trim(), 8); } catch (IllegalArgumentException e) { return 0; } } /** * Returns the name of the file this entry is a link to, or null if this * entry is not a link. */ public String getLinkName() { // if (isLink()) // return null; return new String(linkName).trim(); } /** Returns the modification time of the entry */ public long getTime() { try { return Long.parseLong(new String(mtime).trim(), 8); } catch (IllegalArgumentException e) { return 0; } } /** Returns the string name of the userid */ public String getUname() { return new String(uname).trim(); } /** Returns the string name of the group id */ public String getGname() { return new String(gname).trim(); } /** Returns the numeric userid of the entry */ public int getuid() { try { return Integer.parseInt(new String(uid).trim()); } catch (IllegalArgumentException e) { return -1; } } /** Returns the numeric gid of the entry */ public int getgid() { try { return Integer.parseInt(new String(gid).trim()); } catch (IllegalArgumentException e) { return -1; } } /** Returns true if this entry represents a file */ boolean isFile() { return type == LF_NORMAL || type == LF_OLDNORMAL; } /** Returns true if this entry represents a directory */ boolean isDirectory() { return type == LF_DIR; } /** Returns true if this a hard link (to a file in the archive) */ boolean isLink() { return type == LF_LINK; } /** Returns true if this a symbolic link */ boolean isSymLink() { return type == LF_SYMLINK; } /** Returns true if this entry represents some type of UNIX special file */ boolean isSpecial() { return type == LF_CHR || type == LF_BLK || type == LF_FIFO; } public String toString() { return "TarEntry[" + getName() + ']'; } } /* * Exception for TarFile and TarEntry. $Id: TarException.java,v 1.3 1999/10/06 * 15:13:53 ian Exp $ */ class TarException extends java.io.IOException { public TarException() { super(); } public TarException(String msg) { super(msg); } } /** * Tape Archive Lister, patterned loosely after java.util.ZipFile. Since, unlike * Zip files, there is no central directory, you have to read the entire file * either to be sure of having a particular file's entry, or to know how many * entries there are in the archive. * * @author Ian Darwin, http://www.darwinsys.com/ * @version $Id: TarFile.java,v 1.12 2004/03/07 17:53:15 ian Exp $ */ class TarFile { /** True after we've done the expensive read. */ protected boolean read = false; /** The list of entries found in the archive */ protected Vector list; /** Size of header block. */ public static final int RECORDSIZE = 512; /* Size of each block, in records */ protected int blocking; /* Size of each block, in bytes */ protected int blocksize; /** File containing archive */ protected String fileName; /** Construct (open) a Tar file by name */ public TarFile(String name) { fileName = name; list = new Vector(); } /** Construct (open) a Tar file by File */ public TarFile(java.io.File name) throws IOException { this(name.getCanonicalPath()); } /** The main datastream. */ protected RandomAccessFile is; /** * Read the Tar archive in its entirety. This is semi-lazy evaluation, in * that we don't read the file until we need to. A future revision may use * even lazier evaluation: in getEntry, scan the list and, if not found, * continue reading! For now, just read the whole file. */ protected void readFile() throws IOException, TarException { is = new RandomAccessFile(fileName, "r"); TarEntry hdr; try { do { hdr = new TarEntry(is); if (hdr.getSize() < 0) { System.out.println("Size < 0"); break; } // System.out.println(hdr.toString()); list.addElement(hdr); // Get the size of the entry int nbytes = hdr.getSize(), diff; // Round it up to blocksize. if ((diff = (nbytes % RECORDSIZE)) != 0) { nbytes += RECORDSIZE - diff; } // And skip over the data portion. // System.out.println("Skipping " + nbytes + " bytes"); is.skipBytes(nbytes); } while (true); } catch (EOFException e) { // OK, just stop reading. } // All done, say we've read the contents. read = true; } /* Close the Tar file. */ public void close() { try { is.close(); } catch (IOException e) { // nothing to do } } /* Returns an enumeration of the Tar file entries. */ public Enumeration entries() throws IOException, TarException { if (!read) { readFile(); } return list.elements(); } /** Returns the Tar entry for the specified name, or null if not found. */ public TarEntry getEntry(String name) { for (int i = 0; i < list.size(); i++) { TarEntry e = (TarEntry) list.elementAt(i); if (name.equals(e.getName())) return e; } return null; } /** * Returns an InputStream for reading the contents of the specified entry * from the archive. May cause the entire file to be read. */ public InputStream getInputStream(TarEntry entry) { return null; } /** Returns the path name of the Tar file. */ public String getName() { return fileName; } /** * Returns the number of entries in the Tar archive. May cause the entire * file to be read. XXX Obviously not written yet, sorry. */ public int size() { return 0; } }