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 Chadwick McHenry
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.compiler;
023
024import java.io.File;
025import java.io.FileInputStream;
026import java.io.IOException;
027import java.io.StringReader;
028import java.io.StringWriter;
029import java.util.Enumeration;
030import java.util.Properties;
031import java.util.Vector;
032
033import net.n3.nanoxml.XMLElement;
034
035import org.apache.tools.ant.taskdefs.Execute;
036
037import com.izforge.izpack.util.VariableSubstitutor;
038
039/**
040 * Sets a property by name, or set of properties (from file or resource) in the project. This is
041 * modeled after ant properties
042 * <p>
043 * 
044 * Properties are immutable: once a property is set it cannot be changed. They are most definately
045 * not variable.
046 * <p>
047 * 
048 * There are five ways to set properties:
049 * <ul>
050 * <li>By supplying both the <i>name</i> and <i>value</i> attributes.</li>
051 * <li>By setting the <i>file</i> attribute with the filename of the property file to load. This
052 * property file has the format as defined by the file used in the class java.util.Properties.</li>
053 * <li>By setting the <i>environment</i> attribute with a prefix to use. Properties will be
054 * defined for every environment variable by prefixing the supplied name and a period to the name of
055 * the variable.</li>
056 * </ul>
057 * 
058 * Combinations of the above are considered an error.
059 * <p>
060 * 
061 * The value part of the properties being set, might contain references to other properties. These
062 * references are resolved when the properties are set.
063 * <p>
064 * 
065 * This also holds for properties loaded from a property file.
066 * <p>
067 * 
068 * Properties are case sensitive.
069 * <p>
070 * 
071 * When specifying the environment attribute, it's value is used as a prefix to use when retrieving
072 * environment variables. This functionality is currently only implemented on select platforms.
073 * <p>
074 * 
075 * Thus if you specify environment=&quot;myenv&quot; you will be able to access OS-specific
076 * environment variables via property names &quot;myenv.PATH&quot; or &quot;myenv.TERM&quot;.
077 * <p>
078 * 
079 * Note also that properties are case sensitive, even if the environment variables on your operating
080 * system are not, e.g. it will be ${env.Path} not ${env.PATH} on Windows 2000.
081 * <p>
082 * 
083 * Note that when specifying either the <code>prefix</code> or <code>environment</code>
084 * attributes, if you supply a property name with a final &quot;.&quot; it will not be doubled. ie
085 * environment=&quot;myenv.&quot; will still allow access of environment variables through
086 * &quot;myenv.PATH&quot; and &quot;myenv.TERM&quot;.
087 * <p>
088 */
089public class Property
090{
091
092    protected String name;
093
094    protected String value;
095
096    protected File file;
097
098    // protected String resource;
099    // protected Path classpath;
100    protected String env;
101
102    // protected Reference ref;
103    protected String prefix;
104
105    protected XMLElement xmlProp;
106
107    protected CompilerConfig config;
108    protected Compiler compiler;
109
110    public Property(XMLElement xmlProp, CompilerConfig config)
111    {
112        this.xmlProp = xmlProp;
113        this.config = config;
114        this.compiler = config.getCompiler();
115        name = xmlProp.getAttribute("name");
116        value = xmlProp.getAttribute("value");
117        env = xmlProp.getAttribute("environment");
118        if (env != null && !env.endsWith(".")) env += ".";
119
120        prefix = xmlProp.getAttribute("prefix");
121        if (prefix != null && !prefix.endsWith(".")) prefix += ".";
122
123        String filename = xmlProp.getAttribute("file");
124        if (filename != null) file = new File(filename);
125    }
126
127    /**
128     * get the value of this property
129     * 
130     * @return the current value or the empty string
131     */
132    public String getValue()
133    {
134        return toString();
135    }
136
137    /**
138     * get the value of this property
139     * 
140     * @return the current value or the empty string
141     */
142    public String toString()
143    {
144        return value == null ? "" : value;
145    }
146
147    /**
148     * Set the property in the project to the value. If the task was give a file, resource or env
149     * attribute here is where it is loaded.
150     */
151    public void execute() throws CompilerException
152    {
153        if (name != null)
154        {
155            if (value == null)
156                config.parseError(xmlProp, "You must specify a value with the name attribute");
157        }
158        else
159        {
160            if (file == null && env == null)
161                config.parseError(xmlProp,
162                        "You must specify file, or environment when not using the name attribute");
163        }
164
165        if (file == null && prefix != null)
166            config.parseError(xmlProp, "Prefix is only valid when loading from a file ");
167
168        if ((name != null) && (value != null))
169            addProperty(name, value);
170
171        else if (file != null)
172            loadFile(file);
173
174        else if (env != null) loadEnvironment(env);
175    }
176
177    /**
178     * load properties from a file
179     * 
180     * @param file file to load
181     */
182    protected void loadFile(File file) throws CompilerException
183    {
184        Properties props = new Properties();
185        config.getPackagerListener().packagerMsg("Loading " + file.getAbsolutePath(),
186                PackagerListener.MSG_VERBOSE);
187        try
188        {
189            if (file.exists())
190            {
191                FileInputStream fis = new FileInputStream(file);
192                try
193                {
194                    props.load(fis);
195                }
196                finally
197                {
198                    if (fis != null) fis.close();
199                }
200                addProperties(props);
201            }
202            else
203            {
204                config.getPackagerListener().packagerMsg(
205                        "Unable to find property file: " + file.getAbsolutePath(),
206                        PackagerListener.MSG_VERBOSE);
207            }
208        }
209        catch (IOException ex)
210        {
211            config.parseError(xmlProp, "Faild to load file: " + file.getAbsolutePath(), ex);
212        }
213    }
214
215    /**
216     * load the environment values
217     * 
218     * @param prefix prefix to place before them
219     */
220    protected void loadEnvironment(String prefix) throws CompilerException
221    {
222        Properties props = new Properties();
223        config.getPackagerListener().packagerMsg("Loading Environment " + prefix,
224                PackagerListener.MSG_VERBOSE);
225        Vector osEnv = Execute.getProcEnvironment();
226        for (Enumeration e = osEnv.elements(); e.hasMoreElements();)
227        {
228            String entry = (String) e.nextElement();
229            int pos = entry.indexOf('=');
230            if (pos == -1)
231            {
232                config.getPackagerListener().packagerMsg("Ignoring " + prefix,
233                        PackagerListener.MSG_WARN);
234            }
235            else
236            {
237                props.put(prefix + entry.substring(0, pos), entry.substring(pos + 1));
238            }
239        }
240        addProperties(props);
241    }
242
243    /**
244     * Add a name value pair to the project property set
245     * 
246     * @param name name of property
247     * @param value value to set
248     */
249    protected void addProperty(String name, String value) throws CompilerException
250    {
251        value = compiler.replaceProperties(value);
252
253        compiler.addProperty(name, value);
254    }
255
256    /**
257     * iterate through a set of properties, resolve them then assign them
258     */
259    protected void addProperties(Properties props) throws CompilerException
260    {
261        resolveAllProperties(props);
262        Enumeration e = props.keys();
263        while (e.hasMoreElements())
264        {
265            String name = (String) e.nextElement();
266            String value = props.getProperty(name);
267
268            if (prefix != null)
269            {
270                name = prefix + name;
271            }
272
273            addProperty(name, value);
274        }
275    }
276
277    /**
278     * resolve properties inside a properties object
279     * 
280     * @param props properties to resolve
281     */
282    private void resolveAllProperties(Properties props) throws CompilerException
283    {
284        VariableSubstitutor subs = new VariableSubstitutor(props);
285        subs.setBracesRequired(true);
286
287        for (Enumeration e = props.keys(); e.hasMoreElements();)
288        {
289            String name = (String) e.nextElement();
290            String value = props.getProperty(name);
291
292            int mods = -1;
293            do
294            {
295                StringReader read = new StringReader(value);
296                StringWriter write = new StringWriter();
297
298                try
299                {
300                    mods = subs.substitute(read, write, "at");
301                    // TODO: check for circular references. We need to know
302                    // which
303                    // variables were substituted to do that
304                    props.put(name, value);
305                }
306                catch (IOException ex)
307                {
308                    config.parseError(xmlProp, "Faild to load file: " + file.getAbsolutePath(),
309                            ex);
310                }
311            }
312            while (mods != 0);
313        }
314    }
315}