001/*
002 * $Id: Unpacker.java,v 1.49 2005/08/26 06:48:51 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 2001 Johannes Lehtinen
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 */
022
023package com.izforge.izpack.installer;
024
025import java.io.BufferedInputStream;
026import java.io.BufferedOutputStream;
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.FileNotFoundException;
030import java.io.FileOutputStream;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.ObjectInputStream;
034import java.lang.reflect.Constructor;
035import java.net.URL;
036import java.util.ArrayList;
037import java.util.HashMap;
038import java.util.HashSet;
039import java.util.Iterator;
040import java.util.List;
041import java.util.Stack;
042import java.util.TreeSet;
043import java.util.zip.ZipEntry;
044import java.util.zip.ZipInputStream;
045import java.util.zip.ZipOutputStream;
046
047import org.apache.regexp.RE;
048import org.apache.regexp.RECompiler;
049import org.apache.regexp.RESyntaxException;
050
051import com.izforge.izpack.ExecutableFile;
052import com.izforge.izpack.LocaleDatabase;
053import com.izforge.izpack.Pack;
054import com.izforge.izpack.PackFile;
055import com.izforge.izpack.ParsableFile;
056import com.izforge.izpack.UpdateCheck;
057import com.izforge.izpack.event.InstallerListener;
058import com.izforge.izpack.util.AbstractUIHandler;
059import com.izforge.izpack.util.AbstractUIProgressHandler;
060import com.izforge.izpack.util.FileExecutor;
061import com.izforge.izpack.util.IoHelper;
062import com.izforge.izpack.util.OsConstraint;
063import com.izforge.izpack.util.VariableSubstitutor;
064
065/**
066 * Unpacker class.
067 * 
068 * @author Julien Ponge
069 * @author Johannes Lehtinen
070 */
071public class Unpacker extends Thread
072{
073
074    /** The installdata. */
075    private AutomatedInstallData idata;
076
077    /** The installer listener. */
078    private AbstractUIProgressHandler handler;
079
080    /** The uninstallation data. */
081    private UninstallData udata;
082
083    /** The variables substitutor. */
084    private VariableSubstitutor vs;
085
086    /** The instances of the unpacker objects. */
087    private static HashMap instances = new HashMap();
088
089    /** The absolute path of the installation. (NOT the canonical!) */
090    private File absolute_installpath;
091
092    /** The packs locale database. */
093    private LocaleDatabase langpack = null;
094
095    /** Interrupt flag if global interrupt is desired. */
096    private static boolean interruptDesired = false;
097
098    /** Do not perform a interrupt call. */
099    private static boolean discardInterrupt = false;
100
101    /** The name of the XML file that specifies the panel langpack */
102    private static final String LANG_FILE_NAME = "packsLang.xml";
103
104    public static final String ALIVE = "alive";
105
106    public static final String INTERRUPT = "doInterrupt";
107
108    public static final String INTERRUPTED = "interruppted";
109
110    /**
111     * The constructor.
112     * 
113     * @param idata The installation data.
114     * @param handler The installation progress handler.
115     */
116    public Unpacker(AutomatedInstallData idata, AbstractUIProgressHandler handler)
117    {
118        super("IzPack - Unpacker thread");
119        try
120        {
121            String resource = LANG_FILE_NAME + "_" + idata.localeISO3;
122            this.langpack = new LocaleDatabase(ResourceManager.getInstance().getInputStream(
123                    resource));
124        }
125        catch (Throwable exception)
126        {}
127
128        this.idata = idata;
129        this.handler = handler;
130
131        // Initialize the variable substitutor
132        vs = new VariableSubstitutor(idata.getVariables());
133    }
134
135    /**
136     * Returns a copy of the active unpacker instances.
137     * 
138     * @return a copy of active unpacker instances
139     */
140    public static HashMap getRunningInstances()
141    {
142        synchronized (instances)
143        { // Return a shallow copy to prevent a
144            // ConcurrentModificationException.
145            return (HashMap) (instances.clone());
146        }
147    }
148
149    /**
150     * Adds this to the map of all existent instances of Unpacker.
151     */
152    private void addToInstances()
153    {
154        synchronized (instances)
155        {
156            instances.put(this, ALIVE);
157        }
158    }
159
160    /**
161     * Removes this from the map of all existent instances of Unpacker.
162     */
163    private void removeFromInstances()
164    {
165        synchronized (instances)
166        {
167            instances.remove(this);
168        }
169    }
170
171    /**
172     * Initiate interrupt of all alive Unpacker. This method does not interrupt the Unpacker objects
173     * else it sets only the interrupt flag for the Unpacker objects. The dispatching of interrupt
174     * will be performed by the Unpacker objects self.
175     */
176    private static void setInterruptAll()
177    {
178        synchronized (instances)
179        {
180            Iterator iter = instances.keySet().iterator();
181            while (iter.hasNext())
182            {
183                Object key = iter.next();
184                if (instances.get(key).equals(ALIVE))
185                {
186                    instances.put(key, INTERRUPT);
187                }
188            }
189            // Set global flag to allow detection of it in other classes.
190            // Do not set it to thread because an exec will then be stoped.
191            setInterruptDesired(true);
192        }
193    }
194
195    /**
196     * Initiate interrupt of all alive Unpacker and waits until all Unpacker are interrupted or the
197     * wait time has arrived. If the doNotInterrupt flag in InstallerListener is set to true, the
198     * interrupt will be discarded.
199     * 
200     * @param waitTime wait time in millisecounds
201     * @return true if the interrupt will be performed, false if the interrupt will be discarded
202     */
203    public static boolean interruptAll(long waitTime)
204    {
205        long t0 = System.currentTimeMillis();
206        if (isDiscardInterrupt()) return (false);
207        setInterruptAll();
208        while (!isInterruptReady())
209        {
210            if (System.currentTimeMillis() - t0 > waitTime) return (true);
211            try
212            {
213                Thread.sleep(100);
214            }
215            catch (InterruptedException e)
216            {}
217        }
218        return (true);
219    }
220
221    private static boolean isInterruptReady()
222    {
223        synchronized (instances)
224        {
225            Iterator iter = instances.keySet().iterator();
226            while (iter.hasNext())
227            {
228                Object key = iter.next();
229                if (!instances.get(key).equals(INTERRUPTED)) return (false);
230            }
231            return (true);
232        }
233
234    }
235
236    /**
237     * Sets the interrupt flag for this Unpacker to INTERRUPTED if the previos state was INTERRUPT
238     * or INTERRUPTED and returns whether interrupt was initiate or not.
239     * 
240     * @return whether interrupt was initiate or not
241     */
242    private boolean performInterrupted()
243    {
244        synchronized (instances)
245        {
246            Object doIt = instances.get(this);
247            if (doIt != null && (doIt.equals(INTERRUPT) || doIt.equals(INTERRUPTED)))
248            {
249                instances.put(this, INTERRUPTED);
250                return (true);
251            }
252            return (false);
253        }
254    }
255
256    /**
257     * Returns whether interrupt was initiate or not for this Unpacker.
258     * 
259     * @return whether interrupt was initiate or not
260     */
261    private boolean shouldInterrupt()
262    {
263        synchronized (instances)
264        {
265            Object doIt = instances.get(this);
266            if (doIt != null && (doIt.equals(INTERRUPT) || doIt.equals(INTERRUPTED))) { return (true); }
267            return (false);
268        }
269
270    }
271
272    /** The run method. */
273    public void run()
274    {
275        addToInstances();
276        try
277        {
278            //
279            // Initialisations
280            FileOutputStream out = null;
281            ArrayList parsables = new ArrayList();
282            ArrayList executables = new ArrayList();
283            ArrayList updatechecks = new ArrayList();
284            List packs = idata.selectedPacks;
285            int npacks = packs.size();
286            handler.startAction("Unpacking", npacks);
287            udata = UninstallData.getInstance();
288            // Custom action listener stuff --- load listeners ----
289            List[] customActions = getCustomActions();
290            // Custom action listener stuff --- beforePacks ----
291            informListeners(customActions, InstallerListener.BEFORE_PACKS, idata, new Integer(
292                    npacks), handler);
293
294            // We unpack the selected packs
295            for (int i = 0; i < npacks; i++)
296            {
297                // We get the pack stream
298                int n = idata.allPacks.indexOf(packs.get(i));
299
300                // Custom action listener stuff --- beforePack ----
301                informListeners(customActions, InstallerListener.BEFORE_PACK, packs.get(i),
302                        new Integer(npacks), handler);
303                ObjectInputStream objIn = new ObjectInputStream(getPackAsStream(n));
304
305                // We unpack the files
306                int nfiles = objIn.readInt();
307
308                // We get the internationalized name of the pack
309                final Pack pack = ((Pack) packs.get(i));
310                String stepname = pack.name;// the message to be passed to the
311                // installpanel
312                if (langpack != null && !(pack.id == null || pack.id.equals("")))
313                {
314
315                    final String name = langpack.getString(pack.id);
316                    if (name != null && !name.equals(""))
317                    {
318                        stepname = name;
319                    }
320                }
321                handler.nextStep(stepname, i + 1, nfiles);
322                for (int j = 0; j < nfiles; j++)
323                {
324                    // We read the header
325                    PackFile pf = (PackFile) objIn.readObject();
326
327                    if (OsConstraint.oneMatchesCurrentSystem(pf.osConstraints()))
328                    {
329                        // We translate & build the path
330                        String path = IoHelper.translatePath(pf.getTargetPath(), vs);
331                        File pathFile = new File(path);
332                        File dest = pathFile;
333                        if (!pf.isDirectory()) dest = pathFile.getParentFile();
334
335                        if (!dest.exists())
336                        {
337                            // If there are custom actions which would be called
338                            // at
339                            // creating a directory, create it recursively.
340                            List fileListeners = customActions[customActions.length - 1];
341                            if (fileListeners != null && fileListeners.size() > 0)
342                                mkDirsWithEnhancement(dest, pf, customActions);
343                            else
344                            // Create it in on step.
345                            {
346                                if (!dest.mkdirs())
347                                {
348                                    handler.emitError("Error creating directories",
349                                            "Could not create directory\n" + dest.getPath());
350                                    handler.stopAction();
351                                    return;
352                                }
353                            }
354                        }
355
356                        if (pf.isDirectory()) continue;
357
358                        // Custom action listener stuff --- beforeFile ----
359                        informListeners(customActions, InstallerListener.BEFORE_FILE, pathFile, pf,
360                                null);
361                        // We add the path to the log,
362                        udata.addFile(path);
363
364                        handler.progress(j, path);
365
366                        // if this file exists and should not be overwritten,
367                        // check
368                        // what to do
369                        if ((pathFile.exists()) && (pf.override() != PackFile.OVERRIDE_TRUE))
370                        {
371                            boolean overwritefile = false;
372
373                            // don't overwrite file if the user said so
374                            if (pf.override() != PackFile.OVERRIDE_FALSE)
375                            {
376                                if (pf.override() == PackFile.OVERRIDE_TRUE)
377                                {
378                                    overwritefile = true;
379                                }
380                                else if (pf.override() == PackFile.OVERRIDE_UPDATE)
381                                {
382                                    // check mtime of involved files
383                                    // (this is not 100% perfect, because the
384                                    // already existing file might
385                                    // still be modified but the new installed
386                                    // is just a bit newer; we would
387                                    // need the creation time of the existing
388                                    // file or record with which mtime
389                                    // it was installed...)
390                                    overwritefile = (pathFile.lastModified() < pf.lastModified());
391                                }
392                                else
393                                {
394                                    int def_choice = -1;
395
396                                    if (pf.override() == PackFile.OVERRIDE_ASK_FALSE)
397                                        def_choice = AbstractUIHandler.ANSWER_NO;
398                                    if (pf.override() == PackFile.OVERRIDE_ASK_TRUE)
399                                        def_choice = AbstractUIHandler.ANSWER_YES;
400
401                                    int answer = handler.askQuestion(idata.langpack
402                                            .getString("InstallPanel.overwrite.title")
403                                            + pathFile.getName(), idata.langpack
404                                            .getString("InstallPanel.overwrite.question")
405                                            + pathFile.getAbsolutePath(),
406                                            AbstractUIHandler.CHOICES_YES_NO, def_choice);
407
408                                    overwritefile = (answer == AbstractUIHandler.ANSWER_YES);
409                                }
410
411                            }
412
413                            if (!overwritefile)
414                            {
415                                if (!pf.isBackReference() && !((Pack) packs.get(i)).loose)
416                                    objIn.skip(pf.length());
417                                continue;
418                            }
419
420                        }
421
422                        // We copy the file
423                        out = new FileOutputStream(pathFile);
424                        byte[] buffer = new byte[5120];
425                        long bytesCopied = 0;
426                        InputStream pis = objIn;
427                        if (pf.isBackReference())
428                        {
429                            InputStream is = getPackAsStream(pf.previousPackNumber);
430                            pis = new ObjectInputStream(is);
431                            // must wrap for blockdata use by objectstream
432                            // (otherwise strange result)
433                            // skip on underlaying stream (for some reason not
434                            // possible on ObjectStream)
435                            is.skip(pf.offsetInPreviousPack - 4);
436                            // but the stream header is now already read (== 4
437                            // bytes)
438                        }
439                        else if (((Pack) packs.get(i)).loose)
440                        {
441                            pis = new FileInputStream(pf.sourcePath);
442                        }
443                        while (bytesCopied < pf.length())
444                        {
445                            if (performInterrupted())
446                            { // Interrupt was initiated; perform it.
447                                out.close();
448                                if (pis != objIn) pis.close();
449                                return;
450                            }
451                            int maxBytes = (int) Math.min(pf.length() - bytesCopied, buffer.length);
452                            int bytesInBuffer = pis.read(buffer, 0, maxBytes);
453                            if (bytesInBuffer == -1)
454                                throw new IOException("Unexpected end of stream");
455
456                            out.write(buffer, 0, bytesInBuffer);
457
458                            bytesCopied += bytesInBuffer;
459                        }
460                        // Cleanings
461                        out.close();
462                        if (pis != objIn) pis.close();
463
464                        // Set file modification time if specified
465                        if (pf.lastModified() >= 0) pathFile.setLastModified(pf.lastModified());
466                        // Custom action listener stuff --- afterFile ----
467                        informListeners(customActions, InstallerListener.AFTER_FILE, pathFile, pf,
468                                null);
469
470                    }
471                    else
472                    {
473                        if (!pf.isBackReference()) objIn.skip(pf.length());
474                    }
475                }
476
477                // Load information about parsable files
478                int numParsables = objIn.readInt();
479                for (int k = 0; k < numParsables; k++)
480                {
481                    ParsableFile pf = (ParsableFile) objIn.readObject();
482                    pf.path = IoHelper.translatePath(pf.path, vs);
483                    parsables.add(pf);
484                }
485
486                // Load information about executable files
487                int numExecutables = objIn.readInt();
488                for (int k = 0; k < numExecutables; k++)
489                {
490                    ExecutableFile ef = (ExecutableFile) objIn.readObject();
491                    ef.path = IoHelper.translatePath(ef.path, vs);
492                    if (null != ef.argList && !ef.argList.isEmpty())
493                    {
494                        String arg = null;
495                        for (int j = 0; j < ef.argList.size(); j++)
496                        {
497                            arg = (String) ef.argList.get(j);
498                            arg = IoHelper.translatePath(arg, vs);
499                            ef.argList.set(j, arg);
500                        }
501                    }
502                    executables.add(ef);
503                    if (ef.executionStage == ExecutableFile.UNINSTALL)
504                    {
505                        udata.addExecutable(ef);
506                    }
507                }
508                // Custom action listener stuff --- uninstall data ----
509                handleAdditionalUninstallData(udata, customActions);
510
511                // Load information about updatechecks
512                int numUpdateChecks = objIn.readInt();
513
514                for (int k = 0; k < numUpdateChecks; k++)
515                {
516                    UpdateCheck uc = (UpdateCheck) objIn.readObject();
517
518                    updatechecks.add(uc);
519                }
520
521                objIn.close();
522
523                if (performInterrupted())
524                { // Interrupt was initiated; perform it.
525                    return;
526                }
527
528                // Custom action listener stuff --- afterPack ----
529                informListeners(customActions, InstallerListener.AFTER_PACK, packs.get(i),
530                        new Integer(i), handler);
531            }
532
533            // We use the scripts parser
534            ScriptParser parser = new ScriptParser(parsables, vs);
535            parser.parseFiles();
536            if (performInterrupted())
537            { // Interrupt was initiated; perform it.
538                return;
539            }
540
541            // We use the file executor
542            FileExecutor executor = new FileExecutor(executables);
543            if (executor.executeFiles(ExecutableFile.POSTINSTALL, handler) != 0)
544                handler.emitError("File execution failed", "The installation was not completed");
545
546            if (performInterrupted())
547            { // Interrupt was initiated; perform it.
548                return;
549            }
550
551            // We put the uninstaller (it's not yet complete...)
552            putUninstaller();
553
554            // update checks _after_ uninstaller was put, so we don't delete it
555            performUpdateChecks(updatechecks);
556
557            if (performInterrupted())
558            { // Interrupt was initiated; perform it.
559                return;
560            }
561
562            // Custom action listener stuff --- afterPacks ----
563            informListeners(customActions, InstallerListener.AFTER_PACKS, idata, handler, null);
564            if (performInterrupted())
565            { // Interrupt was initiated; perform it.
566                return;
567            }
568
569            // The end :-)
570            handler.stopAction();
571        }
572        catch (Exception err)
573        {
574            // TODO: finer grained error handling with useful error messages
575            handler.stopAction();
576            handler.emitError("An error occured", err.toString());
577            err.printStackTrace();
578        }
579        finally
580        {
581            removeFromInstances();
582        }
583    }
584
585    /**
586     * @param updatechecks
587     */
588    private void performUpdateChecks(ArrayList updatechecks)
589    {
590        ArrayList include_patterns = new ArrayList();
591        ArrayList exclude_patterns = new ArrayList();
592
593        RECompiler recompiler = new RECompiler();
594
595        this.absolute_installpath = new File(idata.getInstallPath()).getAbsoluteFile();
596
597        // at first, collect all patterns
598        for (Iterator iter = updatechecks.iterator(); iter.hasNext();)
599        {
600            UpdateCheck uc = (UpdateCheck) iter.next();
601
602            if (uc.includesList != null)
603                include_patterns.addAll(preparePatterns(uc.includesList, recompiler));
604
605            if (uc.excludesList != null)
606                exclude_patterns.addAll(preparePatterns(uc.excludesList, recompiler));
607        }
608
609        // do nothing if no update checks were specified
610        if (include_patterns.size() == 0) return;
611
612        // now collect all files in the installation directory and figure
613        // out files to check for deletion
614
615        // use a treeset for fast access
616        TreeSet installed_files = new TreeSet();
617
618        for (Iterator if_it = this.udata.getFilesList().iterator(); if_it.hasNext();)
619        {
620            String fname = (String) if_it.next();
621
622            File f = new File(fname);
623
624            if (!f.isAbsolute())
625            {
626                f = new File(this.absolute_installpath, fname);
627            }
628
629            installed_files.add(f.getAbsolutePath());
630        }
631
632        // now scan installation directory (breadth first), contains Files of
633        // directories to scan
634        // (note: we'll recurse infinitely if there are circular links or
635        // similar nasty things)
636        Stack scanstack = new Stack();
637
638        // contains File objects determined for deletion
639        ArrayList files_to_delete = new ArrayList();
640
641        try
642        {
643            scanstack.add(absolute_installpath);
644
645            while (!scanstack.empty())
646            {
647                File f = (File) scanstack.pop();
648
649                File[] files = f.listFiles();
650
651                if (files == null) { throw new IOException(f.getPath() + "is not a directory!"); }
652
653                for (int i = 0; i < files.length; i++)
654                {
655                    File newf = files[i];
656
657                    String newfname = newf.getPath();
658
659                    // skip files we just installed
660                    if (installed_files.contains(newfname)) continue;
661
662                    if (fileMatchesOnePattern(newfname, include_patterns)
663                            && (!fileMatchesOnePattern(newfname, exclude_patterns)))
664                    {
665                        files_to_delete.add(newf);
666                    }
667
668                    if (newf.isDirectory())
669                    {
670                        scanstack.push(newf);
671                    }
672
673                }
674            }
675        }
676        catch (IOException e)
677        {
678            this.handler.emitError("error while performing update checks", e.toString());
679        }
680
681        for (Iterator f_it = files_to_delete.iterator(); f_it.hasNext();)
682        {
683            File f = (File) f_it.next();
684
685            if (!f.isDirectory())
686            // skip directories - they cannot be removed safely yet
687            {
688                this.handler.emitNotification("deleting " + f.getPath());
689                f.delete();
690            }
691
692        }
693
694    }
695
696    /**
697     * @param filename
698     * @param patterns
699     * 
700     * @return true if the file matched one pattern, false if it did not
701     */
702    private boolean fileMatchesOnePattern(String filename, ArrayList patterns)
703    {
704        // first check whether any include matches
705        for (Iterator inc_it = patterns.iterator(); inc_it.hasNext();)
706        {
707            RE pattern = (RE) inc_it.next();
708
709            if (pattern.match(filename)) { return true; }
710        }
711
712        return false;
713    }
714
715    /**
716     * @param list A list of file name patterns (in ant fileset syntax)
717     * @param recompiler The regular expression compiler (used to speed up RE compiling).
718     * 
719     * @return List of org.apache.regexp.RE
720     */
721    private List preparePatterns(ArrayList list, RECompiler recompiler)
722    {
723        ArrayList result = new ArrayList();
724
725        for (Iterator iter = list.iterator(); iter.hasNext();)
726        {
727            String element = (String) iter.next();
728
729            if ((element != null) && (element.length() > 0))
730            {
731                // substitute variables in the pattern
732                element = this.vs.substitute(element, "plain");
733
734                // check whether the pattern is absolute or relative
735                File f = new File(element);
736
737                // if it is relative, make it absolute and prepend the
738                // installation path
739                // (this is a bit dangerous...)
740                if (!f.isAbsolute())
741                {
742                    element = new File(this.absolute_installpath, element).toString();
743                }
744
745                // now parse the element and construct a regular expression from
746                // it
747                // (we have to parse it one character after the next because
748                // every
749                // character should only be processed once - it's not possible
750                // to get this
751                // correct using regular expression replacing)
752                StringBuffer element_re = new StringBuffer();
753
754                int lookahead = -1;
755
756                int pos = 0;
757
758                while (pos < element.length())
759                {
760                    char c;
761
762                    if (lookahead != -1)
763                    {
764                        c = (char) lookahead;
765                        lookahead = -1;
766                    }
767                    else
768                        c = element.charAt(pos++);
769
770                    switch (c)
771                    {
772                    case '/': {
773                        element_re.append(File.separator);
774                        break;
775                    }
776                    // escape backslash and dot
777                    case '\\':
778                    case '.': {
779                        element_re.append("\\");
780                        element_re.append(c);
781                        break;
782                    }
783                    case '*': {
784                        if (pos == element.length())
785                        {
786                            element_re.append("[^" + File.separator + "]*");
787                            break;
788                        }
789
790                        lookahead = element.charAt(pos++);
791
792                        // check for "**"
793                        if (lookahead == '*')
794                        {
795                            element_re.append(".*");
796                            // consume second star
797                            lookahead = -1;
798                        }
799                        else
800                        {
801                            element_re.append("[^" + File.separator + "]*");
802                            // lookahead stays there
803                        }
804                        break;
805                    }
806                    default: {
807                        element_re.append(c);
808                        break;
809                    }
810                    } // switch
811
812                }
813
814                // make sure that the whole expression is matched
815                element_re.append('$');
816
817                // replace \ by \\ and create a RE from the result
818                try
819                {
820                    result.add(new RE(recompiler.compile(element_re.toString())));
821                }
822                catch (RESyntaxException e)
823                {
824                    this.handler.emitNotification("internal error: pattern \"" + element
825                            + "\" produced invalid RE \"" + f.getPath() + "\"");
826                }
827
828            }
829        }
830
831        return result;
832    }
833
834    /**
835     * Puts the uninstaller.
836     * 
837     * @exception Exception Description of the Exception
838     */
839    private void putUninstaller() throws Exception
840    {
841        // get the uninstaller base, returning if not found so that
842        // idata.uninstallOutJar remains null
843        InputStream[] in = new InputStream[2];
844        in[0] = Unpacker.class.getResourceAsStream("/res/IzPack.uninstaller");
845        if (in[0] == null) return;
846        // The uninstaller extension is facultative; it will be exist only
847        // if a native library was marked for uninstallation.
848        in[1] = Unpacker.class.getResourceAsStream("/res/IzPack.uninstaller-ext");
849
850        // Me make the .uninstaller directory
851        String dest = IoHelper.translatePath("$INSTALL_PATH", vs) + File.separator + "Uninstaller";
852        String jar = dest + File.separator + idata.info.getUninstallerName();
853        File pathMaker = new File(dest);
854        pathMaker.mkdirs();
855
856        // We log the uninstaller deletion information
857        udata.setUninstallerJarFilename(jar);
858        udata.setUninstallerPath(dest);
859
860        // We open our final jar file
861        FileOutputStream out = new FileOutputStream(jar);
862        // Intersect a buffer else byte for byte will be written to the file.
863        BufferedOutputStream bos = new BufferedOutputStream(out);
864        ZipOutputStream outJar = new ZipOutputStream(bos);
865        idata.uninstallOutJar = outJar;
866        outJar.setLevel(9);
867        udata.addFile(jar);
868
869        // We copy the uninstallers
870        HashSet doubles = new HashSet();
871
872        for (int i = 0; i < in.length; ++i)
873        {
874            if (in[i] == null) continue;
875            ZipInputStream inRes = new ZipInputStream(in[i]);
876            ZipEntry zentry = inRes.getNextEntry();
877            while (zentry != null)
878            {
879                // Puts a new entry, but not twice like META-INF
880                if (!doubles.contains(zentry.getName()))
881                {
882                    doubles.add(zentry.getName());
883                    outJar.putNextEntry(new ZipEntry(zentry.getName()));
884
885                    // Byte to byte copy
886                    int unc = inRes.read();
887                    while (unc != -1)
888                    {
889                        outJar.write(unc);
890                        unc = inRes.read();
891                    }
892
893                    // Next one please
894                    inRes.closeEntry();
895                    outJar.closeEntry();
896                }
897                zentry = inRes.getNextEntry();
898            }
899            inRes.close();
900        }
901
902        // We put the langpack
903        InputStream in2 = Unpacker.class.getResourceAsStream("/langpacks/" + idata.localeISO3
904                + ".xml");
905        outJar.putNextEntry(new ZipEntry("langpack.xml"));
906        int read = in2.read();
907        while (read != -1)
908        {
909            outJar.write(read);
910            read = in2.read();
911        }
912        outJar.closeEntry();
913    }
914
915    /**
916     * Returns a stream to a pack, location depending on if it's web based.
917     * 
918     * @param n The pack number.
919     * @return The stream or null if it could not be found.
920     * @exception Exception Description of the Exception
921     */
922    private InputStream getPackAsStream(int n) throws Exception
923    {
924        InputStream in = null;
925
926        String webDirURL = idata.info.getWebDirURL();
927
928        if (webDirURL == null) // local
929        {
930            in = Unpacker.class.getResourceAsStream("/packs/pack" + n);
931        }
932        else
933        // web based
934        {
935            // TODO: Look first in same directory as primary jar
936            // This may include prompting for changing of media
937            // TODO: download and cache them all before starting copy process
938
939            // See compiler.Packager#getJarOutputStream for the counterpart
940            String baseName = idata.info.getInstallerBase();
941            String packURL = webDirURL + "/" + baseName + ".pack" + n + ".jar";
942            URL url = new URL("jar:" + packURL + "!/packs/pack" + n);
943            // JarURLConnection jarConnection = (JarURLConnection)
944            // url.openConnection();
945            // TODO: what happens when using an automated installer?
946            in = new WebAccessor(null).openInputStream(url);
947            // TODO: Fails miserably when pack jars are not found, so this is
948            // temporary
949            if (in == null) throw new FileNotFoundException(url.toString());
950        }
951        if( in != null && idata.info.getPackDecoderClassName() != null )
952        {
953            Class decoder = Class.forName(idata.info.getPackDecoderClassName());
954            Class[] paramsClasses = new Class[1];
955            paramsClasses[0] = Class.forName("java.io.InputStream");
956            Constructor constructor = decoder.getDeclaredConstructor(paramsClasses);
957            // Our first used decoder input stream (bzip2) reads byte for byte from
958            // the source. Therefore we put a buffering stream between it and the
959            // source.
960            InputStream buffer = new BufferedInputStream(in);
961            Object[] params = { buffer };
962            Object instance = null;
963            instance = constructor.newInstance( params);
964            if (!InputStream.class.isInstance(instance))
965                throw new InstallerException(  "'" + idata.info.getPackDecoderClassName()
966                        + "' must be derived from "
967                        + InputStream.class.toString());
968            in = (InputStream) instance;
969
970        }
971        return in;
972    }
973
974    // CUSTOM ACTION STUFF -------------- start -----------------
975
976    /**
977     * Informs all listeners which would be informed at the given action type.
978     * 
979     * @param customActions array of lists with the custom action objects
980     * @param action identifier for which callback should be called
981     * @param firstParam first parameter for the call
982     * @param secondParam second parameter for the call
983     * @param thirdParam third parameter for the call
984     */
985    private void informListeners(List[] customActions, int action, Object firstParam,
986            Object secondParam, Object thirdParam) throws Exception
987    {
988        List listener = null;
989        // select the right action list.
990        switch (action)
991        {
992        case InstallerListener.BEFORE_FILE:
993        case InstallerListener.AFTER_FILE:
994        case InstallerListener.BEFORE_DIR:
995        case InstallerListener.AFTER_DIR:
996            listener = customActions[customActions.length - 1];
997            break;
998        default:
999            listener = customActions[0];
1000            break;
1001        }
1002        if (listener == null) return;
1003        // Iterate the action list.
1004        Iterator iter = listener.iterator();
1005        while (iter.hasNext())
1006        {
1007            if (shouldInterrupt()) return;
1008            InstallerListener il = (InstallerListener) iter.next();
1009            switch (action)
1010            {
1011            case InstallerListener.BEFORE_FILE:
1012                il.beforeFile((File) firstParam, (PackFile) secondParam);
1013                break;
1014            case InstallerListener.AFTER_FILE:
1015                il.afterFile((File) firstParam, (PackFile) secondParam);
1016                break;
1017            case InstallerListener.BEFORE_DIR:
1018                il.beforeDir((File) firstParam, (PackFile) secondParam);
1019                break;
1020            case InstallerListener.AFTER_DIR:
1021                il.afterDir((File) firstParam, (PackFile) secondParam);
1022                break;
1023            case InstallerListener.BEFORE_PACK:
1024                il.beforePack((Pack) firstParam, (Integer) secondParam,
1025                        (AbstractUIProgressHandler) thirdParam);
1026                break;
1027            case InstallerListener.AFTER_PACK:
1028                il.afterPack((Pack) firstParam, (Integer) secondParam,
1029                        (AbstractUIProgressHandler) thirdParam);
1030                break;
1031            case InstallerListener.BEFORE_PACKS:
1032                il.beforePacks((AutomatedInstallData) firstParam, (Integer) secondParam,
1033                        (AbstractUIProgressHandler) thirdParam);
1034                break;
1035            case InstallerListener.AFTER_PACKS:
1036                il.afterPacks((AutomatedInstallData) firstParam,
1037                        (AbstractUIProgressHandler) secondParam);
1038                break;
1039
1040            }
1041        }
1042    }
1043
1044    /**
1045     * Returns the defined custom actions split into types including a constructed type for the file
1046     * related installer listeners.
1047     * 
1048     * @return array of lists of custom action data like listeners
1049     */
1050    private List[] getCustomActions()
1051    {
1052        String[] listenerNames = AutomatedInstallData.CUSTOM_ACTION_TYPES;
1053        List[] retval = new List[listenerNames.length + 1];
1054        int i;
1055        for (i = 0; i < listenerNames.length; ++i)
1056        {
1057            retval[i] = (List) idata.customData.get(listenerNames[i]);
1058            if (retval[i] == null)
1059            // Make a dummy list, then iterator is ever callable.
1060                retval[i] = new ArrayList();
1061        }
1062        if (retval[AutomatedInstallData.INSTALLER_LISTENER_INDEX].size() > 0)
1063        { // Installer listeners exist
1064            // Create file related installer listener list in the last
1065            // element of custom action array.
1066            i = retval.length - 1; // Should be so, but safe is safe ...
1067            retval[i] = new ArrayList();
1068            Iterator iter = ((List) retval[AutomatedInstallData.INSTALLER_LISTENER_INDEX])
1069                    .iterator();
1070            while (iter.hasNext())
1071            {
1072                // If we get a class cast exception many is wrong and
1073                // we must fix it.
1074                InstallerListener li = (InstallerListener) iter.next();
1075                if (li.isFileListener()) retval[i].add(li);
1076            }
1077
1078        }
1079        return (retval);
1080    }
1081
1082    /**
1083     * Adds additional unistall data to the uninstall data object.
1084     * 
1085     * @param udata unistall data
1086     * @param customData array of lists of custom action data like uninstaller listeners
1087     */
1088    private void handleAdditionalUninstallData(UninstallData udata, List[] customData)
1089    {
1090        // Handle uninstall libs
1091        udata.addAdditionalData("__uninstallLibs__",
1092                customData[AutomatedInstallData.UNINSTALLER_LIBS_INDEX]);
1093        // Handle uninstaller listeners
1094        udata.addAdditionalData("uninstallerListeners",
1095                customData[AutomatedInstallData.UNINSTALLER_LISTENER_INDEX]);
1096        // Handle uninstaller jars
1097        udata.addAdditionalData("uninstallerJars",
1098                customData[AutomatedInstallData.UNINSTALLER_JARS_INDEX]);
1099    }
1100
1101    // This method is only used if a file related custom action exist.
1102    /**
1103     * Creates the given directory recursive and calls the method "afterDir" of each listener with
1104     * the current file object and the pack file object. On error an exception is raised.
1105     * 
1106     * @param dest the directory which should be created
1107     * @param pf current pack file object
1108     * @param customActions all defined custom actions
1109     * @return false on error, true else
1110     * @throws Exception
1111     */
1112
1113    private boolean mkDirsWithEnhancement(File dest, PackFile pf, List[] customActions)
1114            throws Exception
1115    {
1116        String path = "unknown";
1117        if (dest != null) path = dest.getAbsolutePath();
1118        if (dest != null && !dest.exists() && dest.getParentFile() != null)
1119        {
1120            if (dest.getParentFile().exists())
1121                informListeners(customActions, InstallerListener.BEFORE_DIR, dest, pf, null);
1122            if (!dest.mkdir())
1123            {
1124                mkDirsWithEnhancement(dest.getParentFile(), pf, customActions);
1125                if (!dest.mkdir()) dest = null;
1126            }
1127            informListeners(customActions, InstallerListener.AFTER_DIR, dest, pf, null);
1128        }
1129        if (dest == null)
1130        {
1131            handler.emitError("Error creating directories", "Could not create directory\n" + path);
1132            handler.stopAction();
1133            return (false);
1134        }
1135        return (true);
1136    }
1137
1138    // CUSTOM ACTION STUFF -------------- end -----------------
1139
1140    /**
1141     * Returns whether an interrupt request should be discarded or not.
1142     * 
1143     * @return Returns the discard interrupt flag
1144     */
1145    public static synchronized boolean isDiscardInterrupt()
1146    {
1147        return discardInterrupt;
1148    }
1149
1150    /**
1151     * Sets the discard interrupt flag.
1152     * 
1153     * @param di the discard interrupt flag to set
1154     */
1155    public static synchronized void setDiscardInterrupt(boolean di)
1156    {
1157        discardInterrupt = di;
1158        setInterruptDesired(false);
1159    }
1160
1161    /**
1162     * Returns the interrupt desired state.
1163     * 
1164     * @return the interrupt desired state
1165     */
1166    public static boolean isInterruptDesired()
1167    {
1168        return interruptDesired;
1169    }
1170
1171    /**
1172     * @param interruptDesired The interrupt desired flag to set
1173     */
1174    private static void setInterruptDesired(boolean interruptDesired)
1175    {
1176        Unpacker.interruptDesired = interruptDesired;
1177    }
1178}