001/*
002 * $Id: PackCompressorBase.java,v 1.2 2005/08/26 11:22:53 bartzkau Exp $
003 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved.
004 * 
005 * http://www.izforge.com/izpack/
006 * http://developer.berlios.de/projects/izpack/
007 * 
008 * Copyright 2005 Klaus Bartz
009 *
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 * 
014 *     http://www.apache.org/licenses/LICENSE-2.0
015 *     
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 */
022package com.izforge.izpack.compressor;
023
024import java.io.BufferedOutputStream;
025import java.io.File;
026import java.io.FileOutputStream;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.lang.reflect.Constructor;
030import java.net.URL;
031import java.net.URLClassLoader;
032
033import com.izforge.izpack.compiler.Compiler;
034
035
036/**
037 * IzPack will be able to support different compression methods for the
038 * packs included in the installation jar file.
039 * This abstract class implements the interface PackCompressor for
040 * the common needed methods.
041 * 
042 * @author Klaus Bartz
043 */
044
045public abstract class PackCompressorBase implements PackCompressor
046{
047
048    protected String [] formatNames = null;
049    protected String [] containerPaths = null;
050    protected String decoderMapper = null;
051    /**
052     * Should contain all full qualified (use dots, not slashes)
053     * names of the class files. Regex will be suported in the
054     * manner of <code>String.match</code>. <br>
055     * Example:
056     * <pre>"org.apache.tools.bzip2.CBZip2InputStream.*"</pre>
057     * Do not forget the dot before the asterix.
058     * For an other example see class BZip2PackCompressor.
059     */
060    protected String [][] decoderClassNames = null;
061    protected String encoderClassName = null;
062    
063    protected Class [] paramsClasses = null;
064
065    private Compiler compiler;
066    private Constructor constructor;
067    private int level = -1;
068   /**
069     * 
070     */
071    public PackCompressorBase()
072    {
073        super();
074    }
075
076    /* (non-Javadoc)
077     * @see com.izforge.izpack.compressor.PackCompressor#getContainerPath()
078     */
079    public String[] getContainerPaths()
080    {
081        return(containerPaths);
082    }
083
084    /* (non-Javadoc)
085     * @see com.izforge.izpack.compressor.PackCompressor#getEncoderClassName()
086     */
087    public String getEncoderClassName()
088    {
089        return(encoderClassName);
090    }
091    /* (non-Javadoc)
092     * @see com.izforge.izpack.compressor.PackCompressor#getDecoderClassNames()
093     */
094    public String[][] getDecoderClassNames()
095    {
096        return(decoderClassNames);
097    }
098
099    /* (non-Javadoc)
100     * @see com.izforge.izpack.compressor.PackCompressor#useStandardCompression()
101     */
102    public boolean useStandardCompression()
103    {
104        return( false );
105    }
106
107    /* (non-Javadoc)
108     * @see com.izforge.izpack.compressor.PackCompressor#getCompressionFormatSymbols()
109     */
110    public String[] getCompressionFormatSymbols()
111    {
112        return(formatNames);
113    }
114
115    /* (non-Javadoc)
116     * @see com.izforge.izpack.compressor.PackCompressor#getDecoderMapperName()
117     */
118    public String getDecoderMapperName()
119    {
120        return(decoderMapper);
121    }
122
123    /* (non-Javadoc)
124     * @see com.izforge.izpack.compressor.PackCompressor#setCompiler(com.izforge.izpack.compiler.Compiler)
125     */
126    public void setCompiler(Compiler compiler)
127    {
128        this.compiler = compiler;
129    }
130
131    /* (non-Javadoc)
132     * @see com.izforge.izpack.compressor.PackCompressor#setCompressionLevel(int)
133     */
134    public void setCompressionLevel(int level)
135    {
136        this.level =  level;
137    }
138
139    /* (non-Javadoc)
140     * @see com.izforge.izpack.compressor.PackCompressor#getCompressionLevel()
141     */
142    public int getCompressionLevel()
143    {
144        return( level);
145    }
146
147    /* (non-Javadoc)
148     * @see com.izforge.izpack.compressor.PackCompressor#needsBufferedOutputStream()
149     */
150    public boolean needsBufferedOutputStream()
151    {
152        return(true);
153    }
154
155
156    /**
157     * Loads the given class from the previos setted container paths.
158     * @param className full qualified name of the class to be loaded
159     * @throws Exception
160     */
161    public void loadClass( String className) throws Exception
162    {
163        if( getEncoderClassName() == null)
164            return;
165        Class encoder = null;
166        if( getContainerPaths() == null  )
167        {   // May be class files are in the compiler.jar.
168            encoder = Class.forName(className);
169        }
170        if( encoder == null)
171        {
172            String [] rawPaths = getContainerPaths();
173            URL [] uRLs = new URL[rawPaths.length];
174            Object instance = null;
175            int i;
176            int j = 0;
177
178            for(i = 0; i < rawPaths.length; ++i)
179            {
180                if( rawPaths[i] == null )
181                    continue;
182                String jarPath = compiler.replaceProperties(rawPaths[i]);
183                URL url = compiler.findIzPackResource(jarPath, "Pack compressor jar file");
184                if (url != null)
185                {
186                    uRLs[j++] = url;
187                    if (getClass().getResource("/" + jarPath) != null)
188                    { // Oops, standalone, URLClassLoader will not work ...
189                        // Write the jar to a temp file.
190                        InputStream in = null;
191                        FileOutputStream outFile = null;
192                        byte[] buffer = new byte[5120];
193                        File tf = null;
194                        try
195                        {
196                            tf = File.createTempFile("izpj", ".jar");
197                            tf.deleteOnExit();
198                            outFile = new FileOutputStream(tf);
199                            in = getClass().getResourceAsStream("/" + jarPath);
200                            long bytesCopied = 0;
201                            int bytesInBuffer;
202                            while ((bytesInBuffer = in.read(buffer)) != -1)
203                            {
204                                outFile.write(buffer, 0, bytesInBuffer);
205                                bytesCopied += bytesInBuffer;
206                            }
207                        }
208                        finally
209                        {
210                            if (in != null) in.close();
211                            if (outFile != null) outFile.close();
212                        }
213                        url = tf.toURL();
214        
215                    }
216                }
217            }
218            if( j > 0 )
219            {
220                if( j < uRLs.length)
221                {
222                    URL [] nurl = new URL[j];
223                    for( i = 0; i < j; ++i)
224                        nurl[i] = uRLs[i];
225                    uRLs = nurl;
226                }
227                // Use the class loader of the interface as parent, else
228                // compile will fail at using it via an Ant task.
229                URLClassLoader ucl = new URLClassLoader(uRLs, PackCompressor.class
230                        .getClassLoader());
231                encoder = ucl.loadClass(className);
232            }
233        }
234
235        if (encoder != null)
236        {
237            // Be aware, paramsClasses should be defined earlier! For
238            // default in the constructor of this class.
239            constructor = encoder.getDeclaredConstructor(paramsClasses);
240        }
241        else
242            compiler.parseError( "Cannot find defined compressor " + className);
243    }
244    
245    /**
246     * Returns a newly created instance of the output stream which should be
247     * used by this pack compressor. This method do not declare the 
248     * return value as FilterOutputStream although there must be an constructor
249     * with a slave output stream as argument. This is done in this way because
250     * some encoding streams from third party are only implemented as 
251     * "normal" output stream.
252     * @param slave output stream to be used as slave
253     * @return a newly created instance of the output stream which should be
254     * used by this pack compressor
255     * @throws Exception
256     */
257    protected OutputStream getOutputInstance(OutputStream slave) 
258        throws Exception
259    {
260        if( needsBufferedOutputStream())
261        {
262            slave = new BufferedOutputStream( slave);
263        }
264        Object [] params = resolveConstructorParams( slave );
265        if( constructor == null )
266            loadClass(getEncoderClassName());
267        if( constructor == null )
268            return(null);
269        Object instance = null;
270        instance = constructor.newInstance( params);
271        if (!OutputStream.class.isInstance(instance))
272            compiler.parseError( "'" + getEncoderClassName() + "' must be derived from "
273                    + OutputStream.class.toString());
274        return((OutputStream) instance );
275        
276    }
277    
278    /**
279     * This method will be used to support different constructor signatures.
280     * The default is 
281     * <pre>XXXOutputStream( OutputStream slave )</pre>
282     * if level is -1 or
283     * <pre>XXXOutputStream( OutputStream slave, int level )</pre>
284     * if level is other than -1.<br>
285     * If the signature of the used output stream will be other, overload
286     * this method in the derived pack compressor class.
287     * @param slave output stream to be used as slave
288     * @return the constructor params as Object [] to be used as construction
289     * of the constructor via reflection
290     * @throws Exception
291     */
292    protected Object[] resolveConstructorParams( OutputStream slave) throws Exception
293    {
294        if( level == -1 )
295        {
296            paramsClasses = new Class[1];
297            paramsClasses[0] = Class.forName("java.io.OutputStream");
298            Object[] params = { slave};
299            return( params );
300        }
301        paramsClasses = new Class[2];
302        paramsClasses[0] = Class.forName("java.io.OutputStream");
303        paramsClasses[1] = java.lang.Integer.TYPE;
304        Object[] params = { slave, new Integer(level)};
305        return( params );
306     }
307
308}