/*
* $Id$
* IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
*
* http://izpack.org/
* http://izpack.codehaus.org/
*
* Copyright 2005 Klaus Bartz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.izforge.izpack.compressor;
import com.izforge.izpack.compiler.Compiler;
import java.io.*;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
/**
* IzPack will be able to support different compression methods for the
* packs included in the installation jar file.
* This abstract class implements the interface PackCompressor for
* the common needed methods.
*
* @author Klaus Bartz
*/
public abstract class PackCompressorBase implements PackCompressor
{
protected String[] formatNames = null;
protected String[] containerPaths = null;
protected String decoderMapper = null;
/**
* Should contain all full qualified (use dots, not slashes)
* names of the class files. Regex will be suported in the
* manner of <code>String.match</code>. <br>
* Example:
* <pre>"org.apache.tools.bzip2.CBZip2InputStream.*"</pre>
* Do not forget the dot before the asterix.
* For an other example see class BZip2PackCompressor.
*/
protected String[][] decoderClassNames = null;
protected String encoderClassName = null;
protected Class[] paramsClasses = null;
private Compiler compiler;
private Constructor<Object> constructor;
private int level = -1;
/**
*
*/
public PackCompressorBase()
{
super();
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#getContainerPath()
*/
public String[] getContainerPaths()
{
return (containerPaths);
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#getEncoderClassName()
*/
public String getEncoderClassName()
{
return (encoderClassName);
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#getDecoderClassNames()
*/
public String[][] getDecoderClassNames()
{
return (decoderClassNames);
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#useStandardCompression()
*/
public boolean useStandardCompression()
{
return (false);
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#getCompressionFormatSymbols()
*/
public String[] getCompressionFormatSymbols()
{
return (formatNames);
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#getDecoderMapperName()
*/
public String getDecoderMapperName()
{
return (decoderMapper);
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#setCompiler(com.izforge.izpack.compiler.Compiler)
*/
public void setCompiler(Compiler compiler)
{
this.compiler = compiler;
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#setCompressionLevel(int)
*/
public void setCompressionLevel(int level)
{
this.level = level;
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#getCompressionLevel()
*/
public int getCompressionLevel()
{
return (level);
}
/* (non-Javadoc)
* @see com.izforge.izpack.compressor.PackCompressor#needsBufferedOutputStream()
*/
public boolean needsBufferedOutputStream()
{
return (true);
}
/**
* Loads the given class from the previos setted container paths.
*
* @param className full qualified name of the class to be loaded
* @throws Exception
*/
public void loadClass(String className) throws Exception
{
if (getEncoderClassName() == null)
{
return;
}
Class<Object> encoder = null;
if (getContainerPaths() == null)
{ // May be class files are in the compiler.jar.
encoder = (Class<Object>) Class.forName(className);
}
if (encoder == null)
{
String[] rawPaths = getContainerPaths();
URL[] uRLs = new URL[rawPaths.length];
Object instance = null;
int i;
int j = 0;
for (i = 0; i < rawPaths.length; ++i)
{
if (rawPaths[i] == null)
{
continue;
}
String jarPath = compiler.replaceProperties(rawPaths[i]);
URL url = compiler.findIzPackResource(jarPath, "Pack compressor jar file");
if (url != null)
{
uRLs[j++] = url;
if (getClass().getResource("/" + jarPath) != null)
{ // Oops, standalone, URLClassLoader will not work ...
// Write the jar to a temp file.
InputStream in = null;
FileOutputStream outFile = null;
byte[] buffer = new byte[5120];
File tf = null;
try
{
tf = File.createTempFile("izpj", ".jar");
tf.deleteOnExit();
outFile = new FileOutputStream(tf);
in = getClass().getResourceAsStream("/" + jarPath);
long bytesCopied = 0;
int bytesInBuffer;
while ((bytesInBuffer = in.read(buffer)) != -1)
{
outFile.write(buffer, 0, bytesInBuffer);
bytesCopied += bytesInBuffer;
}
}
finally
{
if (in != null)
{
in.close();
}
if (outFile != null)
{
outFile.close();
}
}
url = tf.toURI().toURL();
}
}
}
if (j > 0)
{
if (j < uRLs.length)
{
URL[] nurl = new URL[j];
for (i = 0; i < j; ++i)
{
nurl[i] = uRLs[i];
}
uRLs = nurl;
}
// Use the class loader of the interface as parent, else
// compile will fail at using it via an Ant task.
URLClassLoader ucl = new URLClassLoader(uRLs, PackCompressor.class
.getClassLoader());
encoder = (Class<Object>) ucl.loadClass(className);
}
}
if (encoder != null)
{
// Be aware, paramsClasses should be defined earlier! For
// default in the constructor of this class.
constructor = encoder.getDeclaredConstructor(paramsClasses);
}
else
{
compiler.parseError("Cannot find defined compressor " + className);
}
}
/**
* Returns a newly created instance of the output stream which should be
* used by this pack compressor. This method do not declare the
* return value as FilterOutputStream although there must be an constructor
* with a slave output stream as argument. This is done in this way because
* some encoding streams from third party are only implemented as
* "normal" output stream.
*
* @param slave output stream to be used as slave
* @return a newly created instance of the output stream which should be
* used by this pack compressor
* @throws Exception
*/
protected OutputStream getOutputInstance(OutputStream slave)
throws Exception
{
if (needsBufferedOutputStream())
{
slave = new BufferedOutputStream(slave);
}
Object[] params = resolveConstructorParams(slave);
if (constructor == null)
{
loadClass(getEncoderClassName());
}
if (constructor == null)
{
return (null);
}
Object instance = null;
instance = constructor.newInstance(params);
if (!OutputStream.class.isInstance(instance))
{
compiler.parseError("'" + getEncoderClassName() + "' must be derived from "
+ OutputStream.class.toString());
}
return ((OutputStream) instance);
}
/**
* This method will be used to support different constructor signatures.
* The default is
* <pre>XXXOutputStream( OutputStream slave )</pre>
* if level is -1 or
* <pre>XXXOutputStream( OutputStream slave, int level )</pre>
* if level is other than -1.<br>
* If the signature of the used output stream will be other, overload
* this method in the derived pack compressor class.
*
* @param slave output stream to be used as slave
* @return the constructor params as Object [] to be used as construction
* of the constructor via reflection
* @throws Exception
*/
protected Object[] resolveConstructorParams(OutputStream slave) throws Exception
{
if (level == -1)
{
paramsClasses = new Class[1];
paramsClasses[0] = Class.forName("java.io.OutputStream");
Object[] params = {slave};
return (params);
}
paramsClasses = new Class[2];
paramsClasses[0] = Class.forName("java.io.OutputStream");
paramsClasses[1] = java.lang.Integer.TYPE;
Object[] params = {slave, level};
return (params);
}
}