001/*
002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved.
003 * 
004 * http://www.izforge.com/izpack/
005 * http://developer.berlios.de/projects/izpack/
006 * 
007 * Copyright 2002 Elmar Grom
008 * 
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *     
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package com.izforge.izpack.util;
023
024import java.io.BufferedReader;
025import java.io.File;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.util.StringTokenizer;
029
030/*---------------------------------------------------------------------------*/
031/**
032 * The <code>TargetFactory</code> serves as a central mechanism to instantiate OS specific class
033 * flavors, provide OS specific file extension types, default install directories and similar
034 * functionality. In addition it provides services that are related to OS versions and flavors. For
035 * a tutorial on using some of the features in this class see the <A
036 * HREF=doc-files/TargetFactory.html>TargetFactory Tutorial</A>.
037 * 
038 * @version 0.0.1 / 1/3/2002
039 * @author Elmar Grom
040 */
041/*---------------------------------------------------------------------------*/
042/*
043 * $ @design
044 * 
045 * Reports actually observed on some systems:
046 * 
047 * OS OS Name Version Architecture Native Report (ver)
048 * ----------------------------------------------------------------------------------------------------------
049 * Windows 95 Windows 98 Windows 98 4.10 x86 Windows 98 [Version 4.10.1998] Windows-ME Windows Me
050 * 4.90 x86 Windows Millennium [Version 4.90.3000] Windows-NT 3.5 Windows-NT 4.0 Windows NT 4.0 x86
051 * Windows NT Version 4.0 Windows 2000 Windows 2000 5.0 x86 Microsoft Windows 2000 [Version
052 * 5.00.2195] Windows-XP Windows 2000 5.1 x86 Microsoft Windows XP [Version 5.1.2600] Windows-XP
053 * Windows XP 5.1 x86 Mac Mac OS-X Linux Linux 2.4.7-10 i386 Linux Linux 2.4.18-4GB i386 Solaris
054 * 
055 * ---------------------------------------------------------------------------
056 */
057public class TargetFactory
058{
059
060    // ------------------------------------------------------------------------
061    // Constant Definitions
062    // ------------------------------------------------------------------------
063
064    // Basic operating systems
065
066    /** Identifies Microsoft Windows. */
067    public static final int WINDOWS = 0;
068
069    /** Identifies generic UNIX operating systems */
070    public static final int UNIX = 2;
071
072    /** Used to report a non specific operating system. */
073    public static final int GENERIC = 3;
074
075    // operating system favors
076
077    /** This is the basic flavor for every operating system. */
078    public static final int STANDARD = 0;
079
080    /**
081     * Used to identify the Windows-NT class of operating systems in terms of an OS flavor. It is
082     * reported for Windows-NT, 2000 and XP.
083     */
084    public static final int NT = 1;
085
086    /** Used to identify the OS X flavor of the Mac OS */
087    public static final int X = 2;
088
089    // system architecture
090
091    /** Identifies Intel X86 based processor types. */
092    public static final int X86 = 0;
093
094    /** Nonspecific processor architecture, other than X86. */
095    public static final int OTHER = 1;
096
097    /**
098     * The extensions used for native libraries on various operating systems. The string positions
099     * correspond to the basic operating system indexes. The following values are legal to use :
100     * <br>
101     * <br>
102     * <ul>
103     * <li>WINDOWS
104     * <li>MAC
105     * <li>UNIX
106     * <li>GENERIC
107     * </ul>
108     */
109    static final String[] LIBRARY_EXTENSION = { "dll", "so", "", ""};
110
111    /**
112     * The os specific class prefixes for classes that implement different versions for the various
113     * operating systems. The string positions correspond to the basic operating system indexes. The
114     * following values are legal to use : <br>
115     * <br>
116     * <ul>
117     * <li>WINDOWS
118     * <li>MAC
119     * <li>UNIX
120     * <li>GENERIC
121     * </ul>
122     */
123    static final String[] CLASS_PREFIX = { "Win_", "Mac_", "Unix_", ""};
124
125    /**
126     * The os favor specific class prefixes for classes the implement different versions for various
127     * os favors. The string positions correspond to the flavor indexes. The following values are
128     * legal to use : <br>
129     * <br>
130     * <ul>
131     * <li>STANDARD
132     * <li>NT
133     * <li>X
134     * </ul>
135     */
136    static final String[] CLASS_FLAVOR_PREFIX = { "", "NT_", "X_"};
137
138    /**
139     * The list of processor architecture specific prefixes. The string positions correspond to the
140     * architecture indexes. The following values are leegal to use : <br>
141     * <br>
142     * <ul>
143     * <li>X86
144     * <li>OTHER
145     * </ul>
146     */
147    static final String[] CLASS_ARCHITECTURE_PREFIX = { "X86_", // Intel X86
148            // architecture
149            "U_" // unknown
150    };
151
152    /**
153     * The list of default install path fragments. Depending on the operating system, a path
154     * fragment might represent either a part of the default install path or the entire path to use.
155     * For MS-Windows it is always only a part of the full install path. The string positions
156     * correspond to the basic operating system indexes. The following values are leegal to use :
157     * <br>
158     * <br>
159     * <ul>
160     * <li>WINDOWS
161     * <li>MAC
162     * <li>UNIX
163     * <li>GENERIC
164     * </ul>
165     */
166    static final String[] INSTALL_PATH_FRAGMENT = { "Program Files" + File.separator,
167            "/Applications" + File.separator, "/usr/local" + File.separator,
168            File.separator + "apps" + File.separator};
169
170    /**
171     * This is a list of keys to use when looking for resources that define the default install path
172     * to use. The list is organized as two dimensional array of <code>String</code>s. To access
173     * the array, denote the first dimension with the operating system index and the second
174     * dimension with the flavor index. For example to access the key for Windows-NT use
175     * <code>INSTALL_PATH_RESOURCE_KEY[WINDOWS][NT]</code> The array uses a sparse population,
176     * that is, not all array locations actually contain a key. Only locations for which a real
177     * operating system/flavor combination exists are populated. For example, there is no such thing
178     * as <code>INSTALL_PATH_RESOURCE_KEY[UNIX][X]</code>
179     */
180    static final String[][] INSTALL_PATH_RESOURCE_KEY = {
181    // Standard NT X
182            { "TargetPanel.dir.windows", "TargetPanel.dir.windows", ""}, // Windows
183            { "TargetPanel.dir.mac", "", "TargetPanel.dir.macosx"}, // Mac
184            { "TargetPanel.dir.unix", "", ""}, // UNIX
185            { "TargetPanel.dir", "", ""} // Generic
186    };
187
188    /** The delimiter characters used to tokenize version numbers */
189    private static final String VERSION_DELIMITER = ".-";
190
191    // ------------------------------------------------------------------------
192    // Variable Declarations
193    // ------------------------------------------------------------------------
194    /**
195     * The reference to the single instance of <code>TargetFactory</code>. Used in static methods
196     * in place of <code>this</code>.
197     */
198    private static TargetFactory me = null;
199
200    /** identifies the operating system we are running on */
201    private int os = -1;
202
203    /** identifies the operating system favor */
204    private int osFlavor = -1;
205
206    /** identifies the hardware architecture we are running on */
207    private int architecture = -1;
208
209    /** represents the version number of the target system */
210    private String version = "";
211
212    /*--------------------------------------------------------------------------*/
213    /**
214     * Constructor
215     */
216    /*--------------------------------------------------------------------------*/
217    /*
218     * $ @design
219     * 
220     * Identify the following about the target system: - OS type - architecture - version
221     * 
222     * and store this information for later use.
223     * --------------------------------------------------------------------------
224     */
225    private TargetFactory()
226    {
227        version = System.getProperty("os.version");
228
229        // ----------------------------------------------------
230        // test for Windows
231        // ----------------------------------------------------
232        if (OsVersion.IS_WINDOWS)
233        {
234            os = WINDOWS;
235            osFlavor = STANDARD;
236            architecture = X86;
237            String osName = OsVersion.OS_NAME.toLowerCase();
238
239            if (osName.indexOf("nt") > -1)
240            {
241                osFlavor = NT;
242            }
243            else if (osName.indexOf("2000") > -1)
244            {
245                osFlavor = NT;
246            }
247            else if (osName.indexOf("xp") > -1)
248            {
249                osFlavor = NT;
250            }
251        }
252        // ----------------------------------------------------
253        // test for Mac OS
254        // ----------------------------------------------------
255        else if (OsVersion.IS_OSX)
256        {
257            os = X;
258            osFlavor = STANDARD;
259            architecture = OTHER;
260        }
261        // ----------------------------------------------------
262        // what's left should be unix
263        // ----------------------------------------------------
264        else
265        {
266            os = UNIX;
267            osFlavor = STANDARD;
268            architecture = OTHER;
269            String osName = OsVersion.OS_NAME.toLowerCase();
270
271            if (osName.indexOf("x86") > -1)
272            {
273                architecture = X86;
274            }
275        }
276    }
277
278    /*--------------------------------------------------------------------------*/
279    /**
280     * Returns an instance of <code>TargetFactory</code> to use.
281     * 
282     * @return an instance of <code>TargetFactory</code>.
283     */
284    /*--------------------------------------------------------------------------*/
285    public static TargetFactory getInstance()
286    {
287        if (me == null)
288        {
289            me = new TargetFactory();
290        }
291
292        return me;
293    }
294
295    /*--------------------------------------------------------------------------*/
296    /**
297     * This method returns an OS and OS flavor specific instance of the requested class. <br>
298     * <br>
299     * <b>Class Naming Rules</b><br>
300     * Class versions must be named with the OS and OS flavor as prefix. The prefixes are simply
301     * concatenated, with the OS prefix first and the flavor prefix second. Use the following OS
302     * specific prefixes:<br>
303     * <br>
304     * <TABLE BORDER=1>
305     * <TR>
306     * <TH>Operating System</TH>
307     * <TH>Prefix</TH>
308     * </TR>
309     * <TR>
310     * <TD>Microsoft Windows</TD>
311     * <TD>Win_</TD>
312     * </TR>
313     * <TR>
314     * <TD>Mac OS</TD>
315     * <TD>Mac_</TD>
316     * </TR>
317     * <TR>
318     * <TD>UNIX</TD>
319     * <TD>UNIX_</TD>
320     * </TR>
321     * </TABLE><br>
322     * For the different OS flavors, use these prefixes:<br>
323     * <br>
324     * <TABLE BORDER=1>
325     * <TR>
326     * <TH>OS Flavor</TH>
327     * <TH>Prefix</TH>
328     * </TR>
329     * <TR>
330     * <TD>NT</TD>
331     * <TD>NT_</TD>
332     * </TR>
333     * <TR>
334     * <TD>Mac OS X</TD>
335     * <TD>X_</TD>
336     * </TR>
337     * </TABLE> <br>
338     * <br>
339     * <b>Naming Example:</b> <br>
340     * <br>
341     * For the class <code>MyClass</code>, the specific version for Windows NT must be in the
342     * same package as <code>MyClass</code> and the name must be <code>Win_NT_MyClass</code>. A
343     * version that should be instantiated for any non-NT flavor would be called
344     * <code>Win_MyClass</code>. This would also be the version instantiated on Windows NT if the
345     * version <code>Win_NT_MyClass</code> does not exist. <br>
346     * <br>
347     * <b>The Loading Process</b> <br>
348     * <br>
349     * The process is completed after the first successful attempt to load a class. <br>
350     * <ol>
351     * <li>load a version that is OS and OS-Flavor specific
352     * <li>load a version that is OS specific
353     * <li>load the base version (without OS or OS-Flavor prefix)
354     * </ol>
355     * <br>
356     * See the <A HREF=doc-files/TargetFactory.html>TargetFactory Tutorial</A> for more
357     * information.<br>
358     * <br>
359     * 
360     * @param name the fully qualified name of the class to load without the extension.
361     * 
362     * @return An instance of the requested class. Note that specific initialization that can not be
363     * accomplished in the default constructor still needs to be performed before the object can be
364     * used.
365     * 
366     * @exception Exception if all attempts to instantiate class fail
367     */
368    /*--------------------------------------------------------------------------*/
369    public Object makeObject(String name) throws Exception
370    {
371        int nameStart = name.lastIndexOf('.') + 1;
372        String packageName = name.substring(0, nameStart);
373        String className = name.substring(nameStart, name.length());
374        String actualName;
375
376        try
377        {
378            actualName = packageName + CLASS_PREFIX[os] + CLASS_FLAVOR_PREFIX[osFlavor] + className;
379            Class temp = Class.forName(actualName);
380            return temp.newInstance();
381        }
382        catch (Throwable exception1)
383        {
384            try
385            {
386                Class temp = Class.forName(packageName + CLASS_PREFIX[os] + className);
387                return temp.newInstance();
388            }
389            catch (Throwable exception2)
390            {
391                try
392                {
393                    actualName = name;
394                    Class temp = Class.forName(actualName);
395                    return temp.newInstance();
396                }
397                catch (Throwable exception3)
398                {
399                    throw new Exception("can not instantiate class " + name);
400                }
401            }
402        }
403    }
404
405    /*--------------------------------------------------------------------------*/
406    /**
407     * Returns true if the version in the parameter string is higher than the version of the target
408     * os.
409     * 
410     * @param version the version number to compare to
411     * 
412     * @return <code>false</code> if the version of the target system is higher, otherwise
413     * <code>true</code>
414     */
415    /*--------------------------------------------------------------------------*/
416    /*
417     * $ @design
418     * 
419     * Version numbers are assumed to be constructed as follows: - a list of one or more numbers,
420     * separated by periods as in X.X.X. ... or periods and dashes as in X.X.X-Y. ... - the numbers
421     * follow the decimal number system - the left most number is of highest significance
422     * 
423     * The process compares each set of numbers, beginning at the most significant and working down
424     * the ranks (this is working left to right). The process is stopped as soon as the pair of
425     * numbers compaired is not equal. If the numer for the target system is higher, flase is
426     * returned, otherwise true.
427     * --------------------------------------------------------------------------
428     */
429    public boolean versionIsHigher(String version) throws Exception
430    {
431        StringTokenizer targetVersion = new StringTokenizer(this.version, VERSION_DELIMITER);
432        StringTokenizer compareVersion = new StringTokenizer(version, VERSION_DELIMITER);
433
434        int target;
435        int compare;
436
437        while (targetVersion.hasMoreTokens() && compareVersion.hasMoreTokens())
438        {
439            try
440            {
441                target = Integer.parseInt(targetVersion.nextToken());
442                compare = Integer.parseInt(compareVersion.nextToken());
443            }
444            catch (Throwable exception)
445            {
446                throw new Exception("error in version string");
447            }
448
449            if (compare > target)
450            {
451                return true;
452            }
453            else if (target > compare) { return false; }
454        }
455
456        return false;
457    }
458
459    /*--------------------------------------------------------------------------*/
460    /**
461     * Returns the index number for the target operating system that was detected.
462     * 
463     * @return an index number for the OS
464     * 
465     * @see #WINDOWS
466     * @see #UNIX
467     * @see #GENERIC
468     */
469    /*--------------------------------------------------------------------------*/
470    public int getOS()
471    {
472        return os;
473    }
474
475    /*--------------------------------------------------------------------------*/
476    /**
477     * Returns the index number for the operating system flavor that was detected on the target
478     * system.
479     * 
480     * @return an index for the OS flavor
481     * 
482     * @see #STANDARD
483     * @see #NT
484     * @see #X
485     */
486    /*--------------------------------------------------------------------------*/
487    public int getOSFlavor()
488    {
489        return osFlavor;
490    }
491
492    /*--------------------------------------------------------------------------*/
493    /**
494     * Returns an index number that identified the processor architecture of the target system.
495     * 
496     * @return an index for the processor architecture
497     * 
498     * @see #X86
499     * @see #OTHER
500     */
501    /*--------------------------------------------------------------------------*/
502    public int getArchitecture()
503    {
504        return architecture;
505    }
506
507    /*--------------------------------------------------------------------------*/
508    /**
509     * Returns the file extension customarily used on the target OS for dynamically loadable
510     * libraries.
511     * 
512     * @return a <code>String</code> containing the customary library extension for the target OS.
513     * Note that the string might be empty if there no such specific extension for the target OS.
514     */
515    /*--------------------------------------------------------------------------*/
516    public String getNativeLibraryExtension()
517    {
518        return LIBRARY_EXTENSION[os];
519    }
520
521    /*--------------------------------------------------------------------------*/
522    /**
523     * Returns the system dependent default install path. This is typically used to suggest an
524     * istall path to the end user, when performing an installation. The default install path is
525     * assembled form the OS specific path fragment specified in <code>INSTALL_PATH_FRAGMENT</code>,
526     * possibly a drive letter and the application name. The user the option to define resources
527     * that define default paths which differ from the path fragments defined here. The following
528     * resource names will be recognized by this method: <br>
529     * <br>
530     * <ul>
531     * <li><code>TargetPanel.dir.windows</code>
532     * <li><code>TargetPanel.dir.macosx</code>
533     * <li><code>TargetPanel.dir.unix</code>
534     * <li><code>TargetPanel.dir</code> plus the all lower case version of
535     * <code>System.getProperty ("os.name")</code>, with all spaces replaced by an underscore
536     * ('_').
537     * <li><code>TargetPanel.dir</code>
538     * </ul>
539     * 
540     * @param appName the name of the application to install. If no specific resource has been set,
541     * then this name will be appended to the OS specific default path fragment.
542     * 
543     * @return the default install path for the target system
544     */
545    /*--------------------------------------------------------------------------*/
546    /*
547     * $ @design
548     * 
549     * First try to read a path string from a resource file. This approach allows the user to
550     * customize the default install path that is suggested to the end user by IzPack. There are a
551     * number of choices for the naming of this resource, so we need to go through a few steps in
552     * order to exhaust the different possibilities. If this was not successful we use the default
553     * install path that is defined for the operating system we are running on. This path should be
554     * expanded by the application name to form the full path that to returne.
555     * --------------------------------------------------------------------------
556     */
557    public String getDefaultInstallPath(String appName)
558    {
559        String path = null;
560        InputStream input;
561        String keyFragment = "/res/" + INSTALL_PATH_RESOURCE_KEY[GENERIC][STANDARD];
562
563        // ----------------------------------------------------
564        // attempt to get an input stream through a resource
565        // based on a key which is specific to the target OS
566        // ----------------------------------------------------
567        input = getClass().getResourceAsStream("/res/" + INSTALL_PATH_RESOURCE_KEY[os][osFlavor]);
568
569        // ----------------------------------------------------
570        // attempt to get an input stream through a resource
571        // based on a key which is made specific to the target
572        // OS by using the string returned by
573        // System.getProperty ("os.name").toLowerCase ()
574        // ----------------------------------------------------
575        if (input == null)
576        {
577            String key = OsVersion.OS_NAME.toLowerCase().replace(' ', '_'); // avoid
578            // spaces
579            // in
580            // file
581            // names
582            key = keyFragment + key.toLowerCase(); // for consistency among
583            // TargetPanel res files
584            input = TargetFactory.class.getResourceAsStream(key);
585        }
586
587        // ----------------------------------------------------
588        // attempt to get an input stream through a resource
589        // based on a key which is not specific to any target OS
590        // ----------------------------------------------------
591        if (input == null)
592        {
593            input = TargetFactory.class.getResourceAsStream(keyFragment);
594        }
595
596        // ----------------------------------------------------
597        // If we got an input stream try to read the path
598        // from the file
599        // ----------------------------------------------------
600        if (input != null)
601        {
602            InputStreamReader streamReader;
603            BufferedReader reader = null;
604            String line;
605
606            try
607            {
608                streamReader = new InputStreamReader(input);
609                reader = new BufferedReader(streamReader);
610                line = reader.readLine();
611
612                while (line != null)
613                {
614                    line = line.trim();
615                    if (!line.equals(""))
616                    {
617                        break;
618                    }
619                    line = reader.readLine();
620                }
621                path = line;
622            }
623            catch (Throwable exception)
624            {}
625            finally
626            {
627                try
628                {
629                    if (reader != null) reader.close();
630                }
631                catch (Throwable exception)
632                {}
633            }
634        }
635
636        // ----------------------------------------------------
637        // if we were unable to obtain a path from a resource,
638        // use the default for the traget operating system.
639        // ----------------------------------------------------
640        if (path == null || path.equals(""))
641        {
642            path = "";
643
644            // --------------------------------------------------
645            // if we run on windows, we need a valid drive letter
646            // to put in front of the path. The drive that
647            // contains the user's home directory is usually the
648            // drive that also contains the install directory,
649            // so this seems the best choice here.
650            // --------------------------------------------------
651            if (os == WINDOWS)
652            {
653                String home = System.getProperty("user.home");
654                // take everything up to and including the first '\'
655                path = home.substring(0, home.indexOf(File.separatorChar) + 1);
656            }
657
658            path = path + INSTALL_PATH_FRAGMENT[os] + appName;
659        }
660
661        return path;
662    }
663
664    /**
665     * Gets a prefix alias for the current platform. "Win_" on Windows Systems "Win_NT_" on WinNT4,
666     * 2000, XP Mac on Mac Mac_X on macosx and Unix_
667     * 
668     * @return a prefix alias for the current platform
669     */
670
671    public static String getCurrentOSPrefix()
672    {
673        String OSName = System.getProperty("os.name").toLowerCase();
674        String OSArch = System.getProperty("os.arch").toLowerCase();
675        int OS = 0;
676        int OSFlavor = 0;
677        int OSarchitecture = 0;
678        // ----------------------------------------------------
679        // test for Windows
680        // ----------------------------------------------------
681        if (OSName.indexOf("windows") > -1)
682        {
683            OS = WINDOWS;
684            OSFlavor = STANDARD;
685            OSarchitecture = X86;
686
687            if (OSName.indexOf("nt") > -1)
688            {
689                OSFlavor = NT;
690            }
691            else if (OSName.indexOf("2000") > -1)
692            {
693                OSFlavor = NT;
694            }
695            else if (OSName.indexOf("xp") > -1)
696            {
697                OSFlavor = NT;
698            }
699        }
700        // ----------------------------------------------------
701        // test for Mac OS
702        // ----------------------------------------------------
703        else if (OSName.indexOf("mac") > -1)
704        {
705            OS = GENERIC;
706            OSFlavor = STANDARD;
707            OSarchitecture = OTHER;
708
709            if (OSName.indexOf("macosx") > -1)
710            {
711                OSFlavor = X;
712            }
713        }
714        // ----------------------------------------------------
715        // what's left should be unix
716        // ----------------------------------------------------
717        else
718        {
719            OS = UNIX;
720            OSFlavor = STANDARD;
721            OSarchitecture = OTHER;
722
723            if (OSArch.indexOf("86") > -1)
724            {
725                OSarchitecture = X86;
726            }
727        }
728
729        return (CLASS_PREFIX[OS] + CLASS_FLAVOR_PREFIX[OSFlavor]);
730    }
731
732}
733/*---------------------------------------------------------------------------*/