[153800] Installable runtime tar support
diff --git a/plugins/org.eclipse.wst.server.core/schema/installableRuntimes.exsd b/plugins/org.eclipse.wst.server.core/schema/installableRuntimes.exsd
index faa16ea..8be2925 100644
--- a/plugins/org.eclipse.wst.server.core/schema/installableRuntimes.exsd
+++ b/plugins/org.eclipse.wst.server.core/schema/installableRuntimes.exsd
@@ -13,7 +13,8 @@
    <element name="extension">
       <complexType>
          <sequence>
-            <element ref="installableRuntime" minOccurs="1" maxOccurs="unbounded"/>
+            <element ref="installableRuntime" minOccurs="0" maxOccurs="unbounded"/>
+            <element ref="runtime" minOccurs="0" maxOccurs="unbounded"/>
          </sequence>
          <attribute name="point" type="string" use="required">
             <annotation>
@@ -42,7 +43,7 @@
    <element name="installableRuntime">
       <annotation>
          <appInfo>
-            <meta.element labelAttribute="name"/>
+            <meta.element labelAttribute="name" deprecated="true"/>
          </appInfo>
       </annotation>
       <complexType>
@@ -79,6 +80,9 @@
                <documentation>
                   the id of the bundle in which the runtime zip is in
                </documentation>
+               <appInfo>
+                  <meta.attribute deprecated="true"/>
+               </appInfo>
             </annotation>
          </attribute>
          <attribute name="bundleVersion" type="string">
@@ -86,13 +90,57 @@
                <documentation>
                   the optional version of the bundle in which the runtime zip is in
                </documentation>
+               <appInfo>
+                  <meta.attribute deprecated="true"/>
+               </appInfo>
             </annotation>
          </attribute>
-         <attribute name="path" type="string" use="required">
+         <attribute name="path" type="string">
             <annotation>
                <documentation>
                   if bundleId is specified then represents the relative path of the runtime zip inside the bundle, otherwise it assumes a relative path of the runtime archive inside the feature directory
                </documentation>
+               <appInfo>
+                  <meta.attribute deprecated="true"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="runtime">
+      <annotation>
+         <appInfo>
+            <meta.element labelAttribute="id"/>
+         </appInfo>
+      </annotation>
+      <complexType>
+         <attribute name="id" type="string" use="required">
+            <annotation>
+               <documentation>
+                  specifies a unique identifier for this extension point
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="url" type="string" use="required">
+            <annotation>
+               <documentation>
+                  a URL to the runtime download
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="licenseUrl" type="string">
+            <annotation>
+               <documentation>
+                  a URL to the runtime license
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="platform" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
             </annotation>
          </attribute>
       </complexType>
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/InstallableRuntime2.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/InstallableRuntime2.java
new file mode 100644
index 0000000..0843fff
--- /dev/null
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/InstallableRuntime2.java
@@ -0,0 +1,206 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.server.core.internal;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.wst.server.core.internal.tar.TarEntry;
+import org.eclipse.wst.server.core.internal.tar.TarInputStream;
+/**
+ * 
+ */
+public class InstallableRuntime2 implements IInstallableRuntime {
+	private IConfigurationElement element;
+
+	public InstallableRuntime2(IConfigurationElement element) {
+		super();
+		this.element = element;
+	}
+
+	/**
+	 * 
+	 * @return the id
+	 */
+	public String getId() {
+		try {
+			return element.getAttribute("id");
+		} catch (Exception e) {
+			return null;
+		}
+	}
+
+	public String getURL() {
+		try {
+			return element.getAttribute("url");
+		} catch (Exception e) {
+			// ignore
+		}
+		return null;
+	}
+
+	public String getLicenseURL() {
+		try {
+			return element.getAttribute("licenseUrl");
+		} catch (Exception e) {
+			// ignore
+		}
+		return null;
+	}
+
+	/*
+	 * @see IInstallableRuntime#getLicense(IProgressMonitor)
+	 */
+	public String getLicense(IProgressMonitor monitor) throws CoreException {
+		URL url = null;
+		try {
+			url = new URL(getLicenseURL());
+			InputStream in = url.openStream();
+			ByteArrayOutputStream out = new ByteArrayOutputStream();
+			byte[] buf = new byte[8192];
+			copy(buf, in, out);
+			return new String(out.toByteArray());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		
+		return null;
+	}
+
+	/*
+	 * @see IInstallableRuntime#install(IPath)
+	 */
+	public void install(final IPath path) {
+		Job installRuntimeJob = new Job(Messages.jobInstallingRuntime) {
+			public boolean belongsTo(Object family) {
+				return ServerPlugin.PLUGIN_ID.equals(family);
+			}
+			
+			protected IStatus run(IProgressMonitor monitor) {
+				try {
+					install(path, monitor);
+				} catch (CoreException ce) {
+					return ce.getStatus();
+				}
+				
+				return Status.OK_STATUS;
+			}
+		};
+		
+		installRuntimeJob.schedule();
+	}
+
+	private void copy(byte[] buf, InputStream in, OutputStream out) throws IOException {
+		int r = in.read(buf);
+		while (r >= 0) {
+			out.write(buf, 0, r);
+			r = in.read(buf);
+		}
+	}
+
+	/*
+	 * @see IInstallableRuntime#install(IPath, IProgressMonitor)
+	 */
+	public void install(IPath path, IProgressMonitor monitor) throws CoreException {
+		URL url = null;
+		File temp = null;
+		try {
+			url = new URL(getURL());
+			temp = File.createTempFile("runtime", "");
+			temp.deleteOnExit();
+		} catch (IOException e) {
+			throw new CoreException(null);
+		}
+		String name = url.getPath();
+		
+		// download
+		try {
+			InputStream in = url.openStream();
+			FileOutputStream fout = new FileOutputStream(temp);
+			byte[] buf = new byte[8192];
+			copy(buf, in, null);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		
+		try {
+			FileInputStream in = new FileInputStream(temp);
+			if (name.endsWith("zip"))
+				unzip(in, path, monitor);
+			else if (name.endsWith("tar"))
+				untar(in, path, monitor);
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error uncompressing runtime", e);
+			throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0,
+					NLS.bind(Messages.errorInstallingServer, e.getLocalizedMessage()), e));
+		}
+	}
+
+	private void unzip(InputStream in, IPath path, IProgressMonitor monitor) throws IOException {
+		// unzip from bundle into path
+		BufferedInputStream bin = new BufferedInputStream(in);
+		ZipInputStream zin = new ZipInputStream(bin);
+		ZipEntry entry = zin.getNextEntry();
+		byte[] buf = new byte[8192];
+		while (entry != null) {
+			String name = entry.getName();
+			monitor.setTaskName("Unzipping: " + name);
+			
+			if (entry.isDirectory())
+				path.append(name).toFile().mkdirs();
+			else {
+				FileOutputStream fout = new FileOutputStream(path.append(name).toFile());
+				copy(buf, zin, fout);
+			}
+			zin.closeEntry();
+			entry = zin.getNextEntry();
+		}
+		zin.close();
+	}
+
+	protected void untar(InputStream in, IPath path, IProgressMonitor monitor) throws IOException {
+		// untar from bundle into path
+		BufferedInputStream bin = new BufferedInputStream(in);
+		TarInputStream zin = new TarInputStream(bin);
+		TarEntry entry = zin.getNextEntry();
+		byte[] buf = new byte[8192];
+		while (entry != null) {
+			String name = entry.getName();
+			monitor.setTaskName("Untarring: " + name);
+			
+			if (entry.getFileType() == TarEntry.DIRECTORY)
+				path.append(name).toFile().mkdirs();
+			else {
+				FileOutputStream fout = new FileOutputStream(path.append(name).toFile());
+				copy(buf, zin, fout);
+			}
+			zin.close();
+			entry = zin.getNextEntry();
+		}
+		zin.close();
+	}
+
+	public String toString() {
+		return "InstallableRuntime2[" + getId() + "]";
+	}
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java
index c3494ac..9e60286 100644
--- a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java
@@ -1060,7 +1060,12 @@
 		List<IInstallableRuntime> list = new ArrayList<IInstallableRuntime>(size);
 		for (int i = 0; i < size; i++) {
 			try {
-				list.add(new InstallableRuntime(cf[i]));
+				if ("runtime".equals(cf[i].getName())) {
+					String platform = cf[i].getAttribute("platform");
+					//if (platform == null || Swt.g) TODO
+						list.add(new InstallableRuntime2(cf[i]));
+				} else
+					list.add(new InstallableRuntime(cf[i]));
 				Trace.trace(Trace.EXTENSION_POINT, "  Loaded installableRuntime: " + cf[i].getAttribute("id"));
 			} catch (Throwable t) {
 				Trace.trace(Trace.SEVERE, "  Could not load installableRuntime: " + cf[i].getAttribute("id"), t);
diff --git a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarEntry.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarEntry.java
similarity index 93%
rename from plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarEntry.java
rename to plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarEntry.java
index f71ade8..4d97196 100644
--- a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarEntry.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarEntry.java
@@ -8,15 +8,14 @@
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
-package org.eclipse.wst.server.ui.internal.downloadableRuntime;
-
+package org.eclipse.wst.server.core.internal.tar;
 /**
  * Representation of a file in a tar archive.
- * 
- * @since 3.1
+ * <p>
+ * Copied from org.eclipse.ui.internal.wizards.datatransfer.
+ * </p>
  */
-public class TarEntry implements Cloneable
-{	
+public class TarEntry implements Cloneable {
 	private String name;
 	private long mode, time, size;
 	private int type;
diff --git a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarException.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarException.java
similarity index 70%
rename from plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarException.java
rename to plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarException.java
index 7e82894..1a3b1fd 100644
--- a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarException.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarException.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2005 IBM Corporation and others.
+ * Copyright (c) 2004, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -8,12 +8,16 @@
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
-package org.eclipse.wst.server.ui.internal.downloadableRuntime;
+package org.eclipse.wst.server.core.internal.tar;
 
+import java.io.IOException;
 /**
  * Exception generated upon encountering corrupted tar files.
+ * <p>
+ * Copied from org.eclipse.ui.internal.wizards.datatransfer.
+ * </p>
  */
-public class TarException extends Exception {
+public class TarException extends IOException {
 	/**
 	 * Generated serial version UID for this class.
 	 */
@@ -34,14 +38,4 @@
     public TarException(String s) {
     	super(s);
     }
-	
-    /**
-     * Constructs a TarException with the specified detail string.
-     *
-     * @param s the detail string
-     * @param cause the cause
-     */
-    public TarException(String s, Throwable cause) {
-    	super(s, cause);
-    }
-}
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarFile.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarFile.java
similarity index 96%
rename from plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarFile.java
rename to plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarFile.java
index e2f30c2..0d97478 100644
--- a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarFile.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarFile.java
@@ -8,7 +8,7 @@
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
-package org.eclipse.wst.server.ui.internal.downloadableRuntime;
+package org.eclipse.wst.server.core.internal.tar;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -20,8 +20,9 @@
  * Reads a .tar or .tar.gz archive file, providing an index enumeration
  * and allows for accessing an InputStream for arbitrary files in the
  * archive.
- * 
- * @since 3.1
+ * <p>
+ * Copied from org.eclipse.ui.internal.wizards.datatransfer.
+ * </p>
  */
 public class TarFile {
 	private File file;
diff --git a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarInputStream.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarInputStream.java
similarity index 95%
rename from plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarInputStream.java
rename to plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarInputStream.java
index 7df6b1c..1eb16f3 100644
--- a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarInputStream.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/tar/TarInputStream.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2006 IBM Corporation and others.
+ * Copyright (c) 2004, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -8,21 +8,20 @@
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
-package org.eclipse.wst.server.ui.internal.downloadableRuntime;
+package org.eclipse.wst.server.core.internal.tar;
 
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-
 /**
  * Input stream for reading files in ustar format (tar) compatible
  * with the specification in IEEE Std 1003.1-2001.  Also supports
  * long filenames encoded using the GNU @LongLink extension.
- * 
- * @since 3.1
+ * <p>
+ * Copied from org.eclipse.ui.internal.wizards.datatransfer.
+ * </p>
  */
-public class TarInputStream extends FilterInputStream
-{
+public class TarInputStream extends FilterInputStream {
 	private int nextEntry = 0;
 	private int nextEOF = 0;
 	private int filepos = 0;
@@ -243,7 +242,7 @@
 			long fileMode = Long.decode(mode.toString()).longValue();
 			entry.setMode(fileMode);
 		} catch(NumberFormatException nfe) { // TODO
-			throw new TarException("TarImport_invalid_tar_format", nfe);
+			throw new TarException("TarImport_invalid_tar_format" + nfe);
 		}
 		
 		pos = 100 + 24;
@@ -264,7 +263,7 @@
 		try {
 			fileSize = Integer.decode(size.toString()).intValue();
 		} catch(NumberFormatException nfe) { // TODO
-			throw new TarException("DataTransferMessages.TarImport_invalid_tar_format", nfe);
+			throw new TarException("DataTransferMessages.TarImport_invalid_tar_format" + nfe);
 		}
 
 		entry.setSize(fileSize);
diff --git a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/HttpResponse.java b/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/HttpResponse.java
deleted file mode 100644
index fbcf38a..0000000
--- a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/HttpResponse.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007 IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *     IBM Corporation - initial API and implementation
- *******************************************************************************/
-package org.eclipse.wst.server.ui.internal.downloadableRuntime;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-
-import org.eclipse.wst.server.ui.internal.Trace;
-
-public class HttpResponse {
-	protected URLConnection connection;
-
-	protected URL url;
-
-	protected InputStream in;
-
-	protected long lastModified;
-
-	protected long offset;
-
-	protected HttpResponse(URL url) {
-		this.url = url;
-	}
-
-	protected URLConnection getConnection() throws IOException {
-		if (connection == null)
-			connection = url.openConnection();
-		//if (offset > 0)
-		//	connection.setRequestProperty("Range", "bytes=" + offset + "-");
-		return connection;
-	}
-
-	public InputStream getInputStream() throws IOException {
-		if (in == null && url != null) {
-			if (connection == null || offset > 0)
-				connection = getConnection();
-			if (offset > 0)
-				connection.setRequestProperty("Range", "bytes=" + offset + "-");
-			try {
-				in = connection.getInputStream();
-			} catch (IOException ioe) {
-				connection = null;
-				throw ioe;
-			}
-			//checkOffset();
-		}
-		return in;
-	}
-
-	public void close() {
-		if (in != null) {
-			try {
-				in.close();
-			} catch (IOException e) {
-				// ignore
-			}
-			in = null;
-		}
-		if (connection != null) {
-			((HttpURLConnection) connection).disconnect();
-			connection = null;
-		}
-	}
-
-	public long getContentLength() {
-		if (connection != null)
-			return connection.getContentLength();
-		return 0;
-	}
-
-	public int getStatusCode() {
-		try {
-			getConnection();
-			return ((HttpURLConnection) connection).getResponseCode();
-		} catch (IOException e) {
-			Trace.trace(Trace.SEVERE, "Error getting status code", e);
-		}
-		
-		return HttpURLConnection.HTTP_BAD_REQUEST;
-	}
-
-	public String getStatusMessage() {
-		try {
-			if (connection != null)
-				return ((HttpURLConnection) connection).getResponseMessage();
-		} catch (IOException e) {
-			Trace.trace(Trace.SEVERE, "Error getting status message", e);
-		}
-		
-		return "";
-	}
-
-	public long getLastModified() {
-		if (lastModified == 0) {
-			try {
-				if (connection == null)
-					connection = getConnection();
-				
-				lastModified = connection.getLastModified();
-			} catch (IOException e) {
-				Trace.trace(Trace.SEVERE, "Error opening connection", e);
-			}
-		}
-		return lastModified;
-	}
-
-	public void setOffset(long offset) {
-		this.offset = offset;
-	}
-
-/*	private void checkOffset() throws IOException {
-		if (offset == 0)
-			return;
-		String range = connection.getHeaderField("Content-Range");
-		//System.out.println("Content-Range=" + range);
-		if (range == null) {
-			//System.err.println("Server does not support ranges");
-			throw new IOException(Messages.HttpResponse_rangeExpected);
-		} else if (!range.startsWith("bytes " + offset + "-")) {
-			//System.err.println("Server returned wrong range");
-			throw new IOException(Messages.HttpResponse_wrongRange);
-		}
-	}*/
-}
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarOutputStream.java b/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarOutputStream.java
deleted file mode 100644
index 6d51507..0000000
--- a/plugins/org.eclipse.wst.server.ui/serverui/org/eclipse/wst/server/ui/internal/downloadableRuntime/TarOutputStream.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2004, 2006 IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
- *******************************************************************************/
-package org.eclipse.wst.server.ui.internal.downloadableRuntime;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * Output stream for writing ustar archive files (tar) compatible
- * with the specification in IEEE Std 1003.1-2001.
- *
- * @since 3.1
- */
-public class TarOutputStream extends FilterOutputStream {
-	private int byteswritten = 0;
-	private int datapos = 0;
-	private long cursize = 0;
-
-	/**
-	 * Creates a new tar output stream.
-	 * 
-	 * @param out the stream to write to
-	 */
-	public TarOutputStream(OutputStream out) {
-		super(out);
-	}
-
-	/*
-	 * Close the output stream and write any necessary padding.
-	 */
-	public void close() throws IOException {
-		// Spec says to write 1024 bytes of zeros at the end.
-		byte[] zeros = new byte[1024];
-		cursize = 1024;
-		write(zeros, 0, 1024);
-
-		// Default block size for tar files is 10240, so we have to
-		// pad the end of the file to be a multiple of this size.
-		if((byteswritten % 10240) != 0) {
-			int length = 10240 - (byteswritten % 10240);
-			cursize = length;
-			zeros = new byte[length];
-			write(zeros, 0, length);
-		}
-		super.close();
-	}
-
-	/**
-	 * Close the current entry in the tar file.  Must be called
-	 * after each entry is completed.
-	 * 
-	 * @throws IOException
-	 */
-	public void closeEntry() throws IOException {
-		byte[] data = new byte[512];
-		int len = 512 - datapos;
-		if(len > 0 && datapos > 0) {
-			cursize = len;
-			write(data, 0, len);
-		}
-	}
-
-	/**
-	 *  The checksum of a tar file header is simply the sum of the bytes in
-	 *  the header.
-	 * 
-	 * @param header
-	 * @return checksum
-	 */
-	private long headerChecksum(byte[] header) {
-		long sum = 0;
-		for(int i = 0; i < 512; i++) {
-			sum += header[i] & 0xff;
-		}
-		return sum;
-	}
-
-	/**
-	 * Adds an entry for a new file in the tar archive.
-	 * 
-	 * @param e TarEntry describing the file
-	 * @throws IOException
-	 */
-	public void putNextEntry(TarEntry e) throws IOException {
-		byte[] header = new byte[512];
-		String filename = e.getName();
-		String prefix = null;
-		int pos, i;
-		
-		/* Split filename into name and prefix if necessary. */
-		byte[] filenameBytes = filename.getBytes("UTF8"); //$NON-NLS-1$
-		if (filenameBytes.length > 99) {
-			int seppos = filename.lastIndexOf('/');
-			if(seppos == -1) {
-				throw new IOException("filename too long"); //$NON-NLS-1$
-			}
-			prefix = filename.substring(0, seppos);
-			filename = filename.substring(seppos + 1);
-			filenameBytes = filename.getBytes("UTF8"); //$NON-NLS-1$
-			if (filenameBytes.length > 99) {
-				throw new IOException("filename too long"); //$NON-NLS-1$
-			}
-		}
-		
-		/* Filename. */
-		pos = 0;
-		System.arraycopy(filenameBytes, 0, header, 0, filenameBytes.length);
-		pos += 100;
-		
-		/* File mode. */
-		StringBuffer mode = new StringBuffer(Long.toOctalString(e.getMode()));
-		while(mode.length() < 7) {
-			mode.insert(0,'0');
-		}
-		for(i = 0; i < 7; i++) {
-			header[pos + i] = (byte) mode.charAt(i);
-		}
-		pos += 8;
-		
-		/* UID. */
-		header[pos] = '0';
-		pos += 8;
-		
-		/* GID. */
-		header[pos] = '0';
-		pos += 8;
-		
-		/* Length of the file. */
-		String length = Long.toOctalString(e.getSize());
-		for(i = 0; i < length.length(); i++) {
-			header[pos + i] = (byte) length.charAt(i);
-		}
-		pos += 12;
-		
-		/* mtime */
-		String mtime = Long.toOctalString(e.getTime());
-		for(i = 0; i < mtime.length(); i++) {
-			header[pos + i] = (byte) mtime.charAt(i);
-		}
-		pos += 12;
-		
-		/* "Blank" out the checksum. */
-		for(i = 0; i < 8; i++) {
-			header[pos + i] = ' ';
-		}
-		pos += 8;
-		
-		/* Link flag. */
-		header[pos] = (byte) e.getFileType();
-		pos += 1;
-
-		/* Link destination. */
-		pos += 100;
-
-		/* Add ustar header. */
-		String ustar = "ustar 00"; //$NON-NLS-1$
-		for(i = 0; i < ustar.length(); i++) {
-			header[pos + i] = (byte) ustar.charAt(i);
-		}
-		header[pos + 5] = 0;
-		pos += 8;
-		
-		/* Username. */
-		String uname = "nobody"; //$NON-NLS-1$
-		for(i = 0; i < uname.length(); i++) {
-			header[pos + i] = (byte) uname.charAt(i);
-		}
-		pos += 32;
-		
-		
-		/* Group name. */
-		String gname = "nobody"; //$NON-NLS-1$
-		for(i = 0; i < gname.length(); i++) {
-			header[pos + i] = (byte) gname.charAt(i);
-		}
-		pos += 32;
-		
-		/* Device major. */
-		pos += 8;
-
-		/* Device minor. */
-		pos += 8;
-
-		/* File prefix. */
-		if(prefix != null) {
-			byte[] prefixBytes = prefix.getBytes("UTF8"); //$NON-NLS-1$
-			if (prefixBytes.length > 155) {
-				throw new IOException("prefix too large"); //$NON-NLS-1$
-			}
-			System.arraycopy(prefixBytes, 0, header, pos, prefixBytes.length);
-		}
-
-		long sum = headerChecksum(header);
-		pos = 100 + 8 + 8 + 8 + 12 + 12;
-		String sumval = Long.toOctalString(sum);
-		for(i = 0; i < sumval.length(); i++) {
-			header[pos + i] = (byte) sumval.charAt(i);
-		}
-
-		cursize = 512;
-		write(header, 0, 512);
-		
-		cursize = e.getSize();
-	}
-
-	/*
-	 * Writes data for the current file into the archive.
-	 */
-	public void write(byte[] b, int off, int len) throws IOException {
-		super.write(b, off, len);
-		datapos = (datapos + len) % 512;
-		byteswritten += len;
-		cursize -= len;
-		if(cursize < 0) {
-			throw new IOException("too much data written for current file"); //$NON-NLS-1$
-		}
-	}
-}