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 2004 Klaus Bartz
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.File;
025import java.io.FileInputStream;
026import java.io.FileOutputStream;
027import java.io.InputStream;
028import java.util.Iterator;
029import java.util.Vector;
030
031import net.n3.nanoxml.NonValidator;
032import net.n3.nanoxml.StdXMLBuilder;
033import net.n3.nanoxml.StdXMLParser;
034import net.n3.nanoxml.StdXMLReader;
035import net.n3.nanoxml.XMLElement;
036
037import com.izforge.izpack.installer.InstallerException;
038import com.izforge.izpack.installer.ResourceManager;
039
040/**
041 * This class contains some helper methods to simplify handling of xml specification files.
042 * 
043 * @author Klaus Bartz
044 * 
045 */
046public class SpecHelper
047{
048
049    private String specFilename;
050
051    private XMLElement spec;
052
053    private boolean _haveSpec;
054
055    public static final String YES = "yes";
056
057    public static final String NO = "no";
058
059    private static final String PACK_KEY = "pack";
060
061    private static final String PACK_NAME = "name";
062
063    /**
064     * The default constructor.
065     */
066    public SpecHelper()
067    {
068        super();
069    }
070
071    /*--------------------------------------------------------------------------*/
072    /**
073     * Reads the XML specification given by the file name. The result is stored in spec.
074     * 
075     * @exception Exception for any problems in reading the specification
076     */
077    /*--------------------------------------------------------------------------*/
078    public void readSpec(String specFileName) throws Exception
079    {
080        readSpec(specFileName, null);
081    }
082
083    /*--------------------------------------------------------------------------*/
084    /**
085     * Reads the XML specification given by the file name. The result is stored in spec.
086     * 
087     * @exception Exception for any problems in reading the specification
088     */
089    /*--------------------------------------------------------------------------*/
090    public void readSpec(String specFileName, VariableSubstitutor substitutor) throws Exception
091    {
092        // open an input stream
093        InputStream input = null;
094        try
095        {
096            input = getResource(specFileName);
097        }
098        catch (Exception exception)
099        {
100            _haveSpec = false;
101            return;
102        }
103        if (input == null)
104        {
105            _haveSpec = false;
106            return;
107        }
108
109        readSpec(input, substitutor);
110
111        // close the stream
112        input.close();
113        this.specFilename = specFileName;
114    }
115
116    /*--------------------------------------------------------------------------*/
117    /**
118     * Reads the XML specification given by the input stream. The result is stored in spec.
119     * 
120     * @exception Exception for any problems in reading the specification
121     */
122    /*--------------------------------------------------------------------------*/
123    public void readSpec(InputStream input) throws Exception
124    {
125        readSpec(input, null);
126    }
127
128    /*--------------------------------------------------------------------------*/
129    /**
130     * Reads the XML specification given by the input stream. The result is stored in spec.
131     * 
132     * @exception Exception for any problems in reading the specification
133     */
134    /*--------------------------------------------------------------------------*/
135    public void readSpec(InputStream input, VariableSubstitutor substitutor) throws Exception
136    {
137        // first try to substitute the variables
138        if (substitutor != null)
139        {
140            input = substituteVariables(input, substitutor);
141        }
142
143        // initialize the parser
144        StdXMLParser parser = new StdXMLParser();
145        parser.setBuilder(new StdXMLBuilder());
146        parser.setValidator(new NonValidator());
147        parser.setReader(new StdXMLReader(input));
148
149        // get the data
150        spec = (XMLElement) parser.parse();
151        _haveSpec = true;
152    }
153
154    /**
155     * Gets the stream to a resource.
156     * 
157     * @param res The resource id.
158     * @return The resource value, null if not found
159     */
160    public InputStream getResource(String res)
161    {
162        try
163        {
164            // System.out.println ("retrieving resource " + res);
165            return ResourceManager.getInstance().getInputStream(res);
166        }
167        catch (Exception e)
168        { // Cannot catch ResourceNotFoundException because it is not public.
169            return null;
170        }
171    }
172
173    /**
174     * Returns a XML element which represents the pack for the given name.
175     * 
176     * @param packDestName name of the pack which should be returned
177     * @return a XML element which represents the pack for the given name
178     */
179    public XMLElement getPackForName(String packDestName)
180    {
181        Vector packs = getSpec().getChildrenNamed(PACK_KEY);
182        Iterator iter = null;
183        if (packs == null) return (null);
184        iter = packs.iterator();
185        while (iter.hasNext())
186        {
187
188            XMLElement pack = (XMLElement) iter.next();
189            String packName = pack.getAttribute(PACK_NAME);
190            if (packName.equals(packDestName)) return (pack);
191        }
192        return (null);
193
194    }
195
196    /**
197     * Create parse error with consistent messages. Includes file name and line # of parent. It is
198     * an error for 'parent' to be null.
199     * 
200     * @param parent The element in which the error occured
201     * @param message Brief message explaining error
202     */
203    public void parseError(XMLElement parent, String message) throws InstallerException
204    {
205        throw new InstallerException(specFilename + ":" + parent.getLineNr() + ": " + message);
206    }
207
208    /**
209     * Returns true if a specification exist, else false.
210     * 
211     * @return true if a specification exist, else false
212     */
213    public boolean haveSpec()
214    {
215        return _haveSpec;
216    }
217
218    /**
219     * Returns the specification.
220     * 
221     * @return the specification
222     */
223    public XMLElement getSpec()
224    {
225        return spec;
226    }
227
228    /**
229     * Sets the specifaction to the given XML element.
230     * 
231     * @param element
232     */
233    public void setSpec(XMLElement element)
234    {
235        spec = element;
236    }
237
238    /**
239     * Returns a Vector with all leafs of the tree which is described with childdef.
240     * 
241     * @param root the XMLElement which is the current root for the search
242     * @param childdef a String array which describes the tree; the last element contains the leaf
243     * name
244     * @return a Vector of XMLElements of all leafs founded under root
245     */
246    public Vector getAllSubChildren(XMLElement root, String[] childdef)
247    {
248        return (getSubChildren(root, childdef, 0));
249    }
250
251    /**
252     * Returns a Vector with all leafs of the tree which is described with childdef beginning at the
253     * given depth.
254     * 
255     * @param root the XMLElement which is the current root for the search
256     * @param childdef a String array which describes the tree; the last element contains the leaf
257     * name
258     * @param depth depth to start in childdef
259     * @return a Vector of XMLElements of all leafs founded under root
260     */
261    private Vector getSubChildren(XMLElement root, String[] childdef, int depth)
262    {
263        Vector retval = null;
264        Vector retval2 = null;
265        Vector children = root != null ? root.getChildrenNamed(childdef[depth]) : null;
266        if (children == null) return (null);
267        if (depth < childdef.length - 1)
268        {
269            Iterator iter = children.iterator();
270            while (iter.hasNext())
271            {
272                retval2 = getSubChildren((XMLElement) iter.next(), childdef, depth + 1);
273                if (retval2 != null)
274                {
275                    if (retval == null) retval = new Vector();
276                    retval.addAll(retval2);
277                }
278            }
279        }
280        else
281            return (children);
282        return (retval);
283    }
284
285    /**
286     * Creates an temp file in to the substitutor the substituted contents of input writes; close it
287     * and (re)open it as FileInputStream. The file will be deleted on exit.
288     * 
289     * @param input the opened input stream which contents should be substituted
290     * @param substitutor substitutor which should substitute the contents of input
291     * @return a file input stream of the created temporary file
292     * @throws Exception
293     */
294    public InputStream substituteVariables(InputStream input, VariableSubstitutor substitutor)
295            throws Exception
296    {
297        File tempFile = File.createTempFile("izpacksubs", "");
298        FileOutputStream fos = null;
299        tempFile.deleteOnExit();
300        try
301        {
302            fos = new FileOutputStream(tempFile);
303            substitutor.substitute(input, fos, null, null);
304        }
305        finally
306        {
307            if (fos != null) fos.close();
308        }
309        return new FileInputStream(tempFile);
310    }
311
312    /**
313     * Returns whether the value to the given attribute is "yes" or not. If the attribute does not
314     * exist, or the value is not "yes" and not "no", the default value is returned.
315     * 
316     * @param element the XML element which contains the attribute
317     * @param attribute the name of the attribute
318     * @param defaultValue the default value
319     * @return whether the value to the given attribute is "yes" or not
320     */
321    public boolean isAttributeYes(XMLElement element, String attribute, boolean defaultValue)
322    {
323        String value = element.getAttribute(attribute, (defaultValue ? YES : NO));
324        if (value.equalsIgnoreCase(YES)) return true;
325        if (value.equalsIgnoreCase(NO)) return false;
326
327        return defaultValue;
328    }
329
330    /**
331     * Returns the attribute for the given attribute name. If no attribute exist, an
332     * InstallerException with a detail message is thrown.
333     * 
334     * @param element XML element which should contain the attribute
335     * @param attrName key of the attribute
336     * @return the attribute as string
337     * @throws Exception
338     */
339    public String getRequiredAttribute(XMLElement element, String attrName)
340            throws InstallerException
341    {
342        String attr = element.getAttribute(attrName);
343        if (attr == null)
344        {
345            parseError(element, "<" + element.getName() + "> requires attribute '" + attrName
346                    + "'.");
347        }
348        return (attr);
349    }
350}