View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.start;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileFilter;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.Reader;
28  import java.io.StringReader;
29  import java.net.URL;
30  import java.text.CollationKey;
31  import java.text.Collator;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.Comparator;
37  import java.util.Enumeration;
38  import java.util.HashMap;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Locale;
42  import java.util.Map;
43  import java.util.Properties;
44  import java.util.Set;
45  import java.util.StringTokenizer;
46  import java.util.TreeSet;
47  
48  /**
49   * <p>
50   * It allows an application to be started with the command <code>"java -jar start.jar"</code>.
51   * </p>
52   * 
53   * <p>
54   * The behaviour of Main is controlled by the <code>"org/eclipse/start/start.config"</code> file obtained as a resource
55   * or file. This can be overridden with the START system property. The format of each line in this file is:
56   * </p>
57   * 
58   * <p>
59   * Each line contains entry in the format:
60   * </p>
61   * 
62   * <pre>
63   *   SUBJECT [ [!] CONDITION [AND|OR] ]*
64   * </pre>
65   * 
66   * <p>
67   * where SUBJECT:
68   * </p>
69   * <ul>
70   * <li>ends with <code>".class"</code> is the Main class to run.</li>
71   * <li>ends with <code>".xml"</code> is a configuration file for the command line</li>
72   * <li>ends with <code>"/"</code> is a directory from which to add all jar and zip files.</li>
73   * <li>ends with <code>"/*"</code> is a directory from which to add all unconsidered jar and zip files.</li>
74   * <li>ends with <code>"/**"</code> is a directory from which to recursively add all unconsidered jar and zip files.</li>
75   * <li>Containing <code>=</code> are used to assign system properties.</li>
76   * <li>Containing <code>~=</code> are used to assign start properties.</li>
77   * <li>Containing <code>/=</code> are used to assign a canonical path.</li>
78   * <li>all other subjects are treated as files to be added to the classpath.</li>
79   * </ul>
80   * 
81   * <p>
82   * property expansion:
83   * </p>
84   * <ul>
85   * <li><code>${name}</code> is expanded to a start property</li>
86   * <li><code>$(name)</code> is expanded to either a start property or a system property.</li>
87   * <li>The start property <code>${version}</code> is defined as the version of the start.jar</li>
88   * </ul>
89   * 
90   * <p>
91   * Files starting with <code>"/"</code> are considered absolute, all others are relative to the home directory.
92   * </p>
93   * 
94   * <p>
95   * CONDITION is one of:
96   * </p>
97   * <ul>
98   * <li><code>always</code></li>
99   * <li><code>never</code></li>
100  * <li><code>available classname</code> - true if class on classpath</li>
101  * <li><code>property name</code> - true if set as start property</li>
102  * <li><code>system name</code> - true if set as system property</li>
103  * <li><code>exists file</code> - true if file/dir exists</li>
104  * <li><code>java OPERATOR version</code> - java version compared to literal</li>
105  * <li><code>nargs OPERATOR number</code> - number of command line args compared to literal</li>
106  * <li>OPERATOR := one of <code>"&lt;"</code>,<code>"&gt;"</code>,<code>"&lt;="</code>,<code>"&gt;="</code>,
107  * <code>"=="</code>,<code>"!="</code></li>
108  * </ul>
109  * 
110  * <p>
111  * CONDITIONS can be combined with <code>AND</code> <code>OR</code> or <code>!</code>, with <code>AND</code> being the
112  * assume operator for a list of CONDITIONS.
113  * </p>
114  * 
115  * <p>
116  * Classpath operations are evaluated on the fly, so once a class or jar is added to the classpath, subsequent available
117  * conditions will see that class.
118  * </p>
119  * 
120  * <p>
121  * The configuration file may be divided into sections with option names like: [ssl,default]
122  * </p>
123  * 
124  * <p>
125  * Note: a special discovered section identifier <code>[=path_to_directory/*]</code> is allowed to auto-create section
126  * IDs, based on directory names found in the path specified in the "path_to_directory/" part of the identifier.
127  * </p>
128  * 
129  * <p>
130  * Clauses after a section header will only be included if they match one of the tags in the options property. By
131  * default options are set to "default,*" or the OPTIONS property may be used to pass in a list of tags, eg. :
132  * </p>
133  * 
134  * <pre>
135  *    java -jar start.jar OPTIONS=jetty,jsp,ssl
136  * </pre>
137  * 
138  * <p>
139  * The tag '*' is always appended to the options, so any section with the * tag is always applied.
140  * </p>
141  * 
142  * <p>
143  * The property map maintained by this class is static and shared between all instances in the same classloader
144  * </p>
145  */
146 public class Config
147 {
148     public static final String DEFAULT_SECTION = "";
149     static
150     {
151         String ver = System.getProperty("jetty.version", null);
152         
153         if(ver == null) {
154             Package pkg = Config.class.getPackage();
155             if (pkg != null && 
156                     "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
157                     (pkg.getImplementationVersion() != null))
158             {
159                 ver = pkg.getImplementationVersion();
160             }
161         }
162 
163         if (ver == null)
164         {
165             ver = "Unknown";
166         }
167         _version = ver;
168     }
169 
170     /**
171      * Natural language sorting for key names.
172      */
173     private final Comparator<String> keySorter = new Comparator<String>()
174     {
175         private final Collator collator = Collator.getInstance();
176 
177         public int compare(String o1, String o2)
178         {
179             CollationKey key1 = collator.getCollationKey(o1);
180             CollationKey key2 = collator.getCollationKey(o2);
181             return key1.compareTo(key2);
182         }
183     };
184 
185     private static final String _version;
186     private static boolean DEBUG = false;
187     private static final Map<String, String> __properties = new HashMap<String, String>();
188     private final Map<String, Classpath> _classpaths = new HashMap<String, Classpath>();
189     private final List<String> _xml = new ArrayList<String>();
190     private String _classname = null;
191 
192     private int argCount = 0;
193     
194     private final Set<String> _activeOptions = new TreeSet<String>(new Comparator<String>()
195     {
196         // Make sure "*" is always at the end of the list
197         public int compare(String o1, String o2)
198         {
199             if ("*".equals(o1))
200             {
201                 return 1;
202             }
203             if ("*".equals(o2))
204             {
205                 return -1;
206             }
207             return o1.compareTo(o2);
208         }
209     });
210 
211     private boolean addClasspathComponent(List<String> sections, String component)
212     {
213         for (String section : sections)
214         {
215             Classpath cp = _classpaths.get(section);
216             if (cp == null)
217                 cp = new Classpath();
218 
219             boolean added = cp.addComponent(component);
220             _classpaths.put(section,cp);
221 
222             if (!added)
223             {
224                 // First failure means all failed.
225                 return false;
226             }
227         }
228 
229         return true;
230     }
231 
232     private boolean addClasspathPath(List<String> sections, String path)
233     {
234         for (String section : sections)
235         {
236             Classpath cp = _classpaths.get(section);
237             if (cp == null)
238             {
239                 cp = new Classpath();
240             }
241             if (!cp.addClasspath(path))
242             {
243                 // First failure means all failed.
244                 return false;
245             }
246             _classpaths.put(section,cp);
247         }
248 
249         return true;
250     }
251 
252     private void addJars(List<String> sections, File dir, boolean recurse) throws IOException
253     {
254         List<File> entries = new ArrayList<File>();
255         File[] files = dir.listFiles();
256         if (files == null)
257         {
258             // No files found, skip it.
259             return;
260         }
261         entries.addAll(Arrays.asList(files));
262         Collections.sort(entries,FilenameComparator.INSTANCE);
263 
264         for (File entry : entries)
265         {
266             if (entry.isDirectory())
267             {
268                 if (recurse)
269                     addJars(sections,entry,recurse);
270             }
271             else
272             {
273                 String name = entry.getName().toLowerCase(Locale.ENGLISH);
274                 if (name.endsWith(".jar") || name.endsWith(".zip"))
275                 {
276                     String jar = entry.getCanonicalPath();
277                     boolean added = addClasspathComponent(sections,jar);
278                     debug((added?"  CLASSPATH+=":"  !") + jar);
279                 }
280             }
281         }
282     }
283 
284     private void close(InputStream stream)
285     {
286         if (stream == null)
287             return;
288 
289         try
290         {
291             stream.close();
292         }
293         catch (IOException ignore)
294         {
295             /* ignore */
296         }
297     }
298 
299     private void close(Reader reader)
300     {
301         if (reader == null)
302             return;
303 
304         try
305         {
306             reader.close();
307         }
308         catch (IOException ignore)
309         {
310             /* ignore */
311         }
312     }
313 
314     public static boolean isDebug()
315     {
316         return DEBUG;
317     }
318 
319     public static void debug(String msg)
320     {
321         if (DEBUG)
322         {
323             System.err.println(msg);
324         }
325     }
326 
327     public static void debug(Throwable t)
328     {
329         if (DEBUG)
330         {
331             t.printStackTrace(System.err);
332         }
333     }
334 
335     private String expand(String s)
336     {
337         int i1 = 0;
338         int i2 = 0;
339         while (s != null)
340         {
341             i1 = s.indexOf("$(",i2);
342             if (i1 < 0)
343                 break;
344             i2 = s.indexOf(")",i1 + 2);
345             if (i2 < 0)
346                 break;
347             String name = s.substring(i1 + 2,i2);
348             String property = getProperty(name);
349             s = s.substring(0,i1) + property + s.substring(i2 + 1);
350         }
351 
352         i1 = 0;
353         i2 = 0;
354         while (s != null)
355         {
356             i1 = s.indexOf("${",i2);
357             if (i1 < 0)
358                 break;
359             i2 = s.indexOf("}",i1 + 2);
360             if (i2 < 0)
361                 break;
362             String name = s.substring(i1 + 2,i2);
363             String property = getProperty(name);
364             s = s.substring(0,i1) + property + s.substring(i2 + 1);
365         }
366 
367         return s;
368     }
369 
370     /**
371      * Get the default classpath.
372      * 
373      * @return the default classpath
374      */
375     public Classpath getClasspath()
376     {
377         return _classpaths.get(DEFAULT_SECTION);
378     }
379 
380     /**
381      * Get the active classpath, as dictated by OPTIONS= entries.
382      * 
383      * @return the Active classpath
384      * @see #getCombinedClasspath(Collection)
385      */
386     public Classpath getActiveClasspath()
387     {
388         return getCombinedClasspath(_activeOptions);
389     }
390 
391     /**
392      * Get the combined classpath representing the default classpath plus all named sections.
393      * 
394      * NOTE: the default classpath will be prepended, and the '*' classpath will be appended.
395      * 
396      * @param optionIds
397      *            the list of section ids to fetch
398      * @return the {@link Classpath} representing combination all of the selected sectionIds, combined with the default
399      *         section id, and '*' special id.
400      */
401     public Classpath getCombinedClasspath(Collection<String> optionIds)
402     {
403         Classpath cp = new Classpath();
404 
405         cp.overlay(_classpaths.get(DEFAULT_SECTION));
406         for (String optionId : optionIds)
407         {
408             Classpath otherCp = _classpaths.get(optionId);
409             if (otherCp == null)
410             {
411                 throw new IllegalArgumentException("No such OPTIONS: " + optionId);
412             }
413             cp.overlay(otherCp);
414         }
415         cp.overlay(_classpaths.get("*"));
416         return cp;
417     }
418 
419     public String getMainClassname()
420     {
421         return _classname;
422     }
423 
424     public static void clearProperties()
425     {
426         __properties.clear();
427     }
428     
429     public static Properties getProperties()
430     {
431         Properties properties = new Properties();
432         // Add System Properties First
433         Enumeration<?> ensysprop = System.getProperties().propertyNames();
434         while(ensysprop.hasMoreElements()) {
435             String name = (String)ensysprop.nextElement();
436             properties.put(name, System.getProperty(name));
437         }
438         // Add Config Properties Next (overwriting any System Properties that exist)
439         for (String key : __properties.keySet()) {
440             properties.put(key,__properties.get(key));
441         }
442         return properties;
443     }
444     
445     public static String getProperty(String name)
446     {
447         if ("version".equalsIgnoreCase(name)) {
448             return _version;
449         }
450         // Search Config Properties First
451         if (__properties.containsKey(name)) {
452             return __properties.get(name);
453         }
454         // Return what exists in System.Properties otherwise.
455         return System.getProperty(name);
456     }
457 
458     public static String getProperty(String name, String defaultValue)
459     {
460         // Search Config Properties First
461         if (__properties.containsKey(name))
462             return __properties.get(name);
463         // Return what exists in System.Properties otherwise.
464         return System.getProperty(name, defaultValue);
465     }
466 
467     /**
468      * Get the classpath for the named section
469      * 
470      * @param sectionId
471      * @return the classpath for the specified section id
472      */
473     public Classpath getSectionClasspath(String sectionId)
474     {
475         return _classpaths.get(sectionId);
476     }
477 
478     /**
479      * Get the list of section Ids.
480      * 
481      * @return the set of unique section ids
482      */
483     public Set<String> getSectionIds()
484     {
485         Set<String> ids = new TreeSet<String>(keySorter);
486         ids.addAll(_classpaths.keySet());
487         return ids;
488     }
489 
490     public List<String> getXmlConfigs()
491     {
492         return _xml;
493     }
494 
495     private boolean isAvailable(List<String> options, String classname)
496     {
497         // Try default/parent class loader first.
498         try
499         {
500             Class.forName(classname);
501             return true;
502         }
503         catch (NoClassDefFoundError e)
504         {
505             debug(e);
506         }
507         catch (ClassNotFoundException e)
508         {
509             debug("ClassNotFoundException (parent class loader): " + classname);
510         }
511 
512         // Try option classloaders instead
513         ClassLoader loader;
514         Classpath classpath;
515         for (String optionId : options)
516         {
517             classpath = _classpaths.get(optionId);
518             if (classpath == null)
519             {
520                 // skip, no classpath
521                 continue;
522             }
523 
524             loader = classpath.getClassLoader();
525 
526             try
527             {
528                 loader.loadClass(classname);
529                 return true;
530             }
531             catch (NoClassDefFoundError e)
532             {
533                 debug(e);
534             }
535             catch (ClassNotFoundException e)
536             {
537                 debug("ClassNotFoundException (section class loader: " + optionId + "): " + classname);
538             }
539         }
540         return false;
541     }
542 
543     /**
544      * Parse the configuration
545      * 
546      * @param buf
547      * @throws IOException
548      */
549     public void parse(CharSequence buf) throws IOException
550     {
551         parse(new StringReader(buf.toString()));
552     }
553 
554     /**
555      * Parse the configuration
556      * 
557      * @param stream the stream to read from
558      * @throws IOException
559      */
560     public void parse(InputStream stream) throws IOException
561     {
562         InputStreamReader reader = null;
563         try
564         {
565             reader = new InputStreamReader(stream);
566             parse(reader);
567         }
568         finally
569         {
570             close(reader);
571         }
572     }
573 
574     /**
575      */
576     public void parse(Reader reader) throws IOException
577     {
578         BufferedReader buf = null;
579 
580         try
581         {
582             buf = new BufferedReader(reader);
583 
584             List<String> options = new ArrayList<String>();
585             options.add(DEFAULT_SECTION);
586             _classpaths.put(DEFAULT_SECTION,new Classpath());
587             Version java_version = new Version(System.getProperty("java.version"));
588             Version ver = new Version();
589 
590             String line = null;
591             while ((line = buf.readLine()) != null)
592             {
593                 String trim = line.trim();
594                 if (trim.length() == 0) // empty line
595                     continue;
596 
597                 if (trim.startsWith("#")) // comment
598                     continue;
599 
600                 // handle options
601                 if (trim.startsWith("[") && trim.endsWith("]"))
602                 {
603                     String identifier = trim.substring(1,trim.length() - 1);
604 
605                     // Normal case: section identifier (possibly separated by commas)
606                     options = Arrays.asList(identifier.split(","));
607                     List<String> option_ids=new ArrayList<String>();
608                     
609                     // Ensure section classpaths exist
610                     for (String optionId : options)
611                     {
612                         if (optionId.charAt(0) == '=')
613                             continue;
614 
615                         if (!_classpaths.containsKey(optionId))
616                             _classpaths.put(optionId,new Classpath());
617                         
618                         if (!option_ids.contains(optionId))
619                             option_ids.add(optionId);
620                     }
621                     
622 
623                     // Process Dynamic
624                     for (String optionId : options)
625                     {
626                         if (optionId.charAt(0) != '=')
627                             continue;
628                         
629                         option_ids = processDynamicSectionIdentifier(optionId.substring(1),option_ids);
630                     }
631                     
632                     options = option_ids;
633                     
634                     continue;
635                 }
636 
637                 try
638                 {
639                     StringTokenizer st = new StringTokenizer(line);
640                     String subject = st.nextToken();
641                     boolean expression = true;
642                     boolean not = false;
643                     String condition = null;
644                     // Evaluate all conditions
645                     while (st.hasMoreTokens())
646                     {
647                         condition = st.nextToken();
648                         if (condition.equalsIgnoreCase("!"))
649                         {
650                             not = true;
651                             continue;
652                         }
653                         if (condition.equalsIgnoreCase("OR"))
654                         {
655                             if (expression)
656                                 break;
657                             expression = true;
658                             continue;
659                         }
660                         if (condition.equalsIgnoreCase("AND"))
661                         {
662                             if (!expression)
663                                 break;
664                             continue;
665                         }
666                         boolean eval = true;
667                         if (condition.equals("true") || condition.equals("always"))
668                         {
669                             eval = true;
670                         }
671                         else if (condition.equals("false") || condition.equals("never"))
672                         {
673                             eval = false;
674                         }
675                         else if (condition.equals("available"))
676                         {
677                             String class_to_check = st.nextToken();
678                             eval = isAvailable(options,class_to_check);
679                         }
680                         else if (condition.equals("exists"))
681                         {
682                             try
683                             {
684                                 eval = false;
685                                 File file = new File(expand(st.nextToken()));
686                                 eval = file.exists();
687                             }
688                             catch (Exception e)
689                             {
690                                 debug(e);
691                             }
692                         }
693                         else if (condition.equals("property"))
694                         {
695                             String property = getProperty(st.nextToken());
696                             eval = property != null && property.length() > 0;
697                         }
698                         else if (condition.equals("system"))
699                         {
700                             String property = System.getProperty(st.nextToken());
701                             eval = property != null && property.length() > 0;
702                         }
703                         else if (condition.equals("java"))
704                         {
705                             String operator = st.nextToken();
706                             String version = st.nextToken();
707                             ver.parse(version);
708                             eval = (operator.equals("<") && java_version.compare(ver) < 0) || (operator.equals(">") && java_version.compare(ver) > 0)
709                             || (operator.equals("<=") && java_version.compare(ver) <= 0) || (operator.equals("=<") && java_version.compare(ver) <= 0)
710                             || (operator.equals("=>") && java_version.compare(ver) >= 0) || (operator.equals(">=") && java_version.compare(ver) >= 0)
711                             || (operator.equals("==") && java_version.compare(ver) == 0) || (operator.equals("!=") && java_version.compare(ver) != 0);
712                         }
713                         else if (condition.equals("nargs"))
714                         {
715                             String operator = st.nextToken();
716                             int number = Integer.parseInt(st.nextToken());
717                             eval = (operator.equals("<") && argCount < number) || (operator.equals(">") && argCount > number)
718                             || (operator.equals("<=") && argCount <= number) || (operator.equals("=<") && argCount <= number)
719                             || (operator.equals("=>") && argCount >= number) || (operator.equals(">=") && argCount >= number)
720                             || (operator.equals("==") && argCount == number) || (operator.equals("!=") && argCount != number);
721                         }
722                         else
723                         {
724                             System.err.println("ERROR: Unknown condition: " + condition);
725                             eval = false;
726                         }
727                         expression &= not?!eval:eval;
728                         not = false;
729                     }
730 
731                     String file = expand(subject);
732                     debug((expression?"T ":"F ") + line);
733                     if (!expression)
734                         continue;
735 
736                     // Setting of a start property
737                     if (subject.indexOf("~=") > 0)
738                     {
739                         int i = file.indexOf("~=");
740                         String property = file.substring(0,i);
741                         String value = fixPath(file.substring(i + 2));
742                         debug("  " + property + "~=" + value);
743                         setProperty(property,value);
744                         continue;
745                     }
746 
747                     // Setting of start property with canonical path
748                     if (subject.indexOf("/=") > 0)
749                     {
750                         int i = file.indexOf("/=");
751                         String property = file.substring(0,i);
752                         String value = fixPath(file.substring(i + 2));
753                         String canonical = new File(value).getCanonicalPath();
754                         debug("  " + property + "/=" + value + "==" + canonical);
755                         setProperty(property,canonical);
756                         continue;
757                     }
758 
759                     // Setting of system property
760                     if (subject.indexOf("=") > 0)
761                     {
762                         int i = file.indexOf("=");
763                         String property = file.substring(0,i);
764                         String value = fixPath(file.substring(i + 1));
765                         debug("  " + property + "=" + value);
766                         System.setProperty(property,value);
767                         continue;
768                     }
769 
770                     // Add all unconsidered JAR and ZIP files to classpath
771                     if (subject.endsWith("/*"))
772                     {
773                         // directory of JAR files - only add jars and zips within the directory
774                         File dir = new File(fixPath(file.substring(0,file.length() - 1)));
775                         addJars(options,dir,false);
776                         continue;
777                     }
778 
779                     // Recursively add all unconsidered JAR and ZIP files to classpath
780                     if (subject.endsWith("/**"))
781                     {
782                         //directory hierarchy of jar files - recursively add all jars and zips in the hierarchy
783                         File dir = new File(fixPath(file.substring(0,file.length() - 2)));
784                         addJars(options,dir,true);
785                         continue;
786                     }
787 
788                     // Add raw classpath directory to classpath
789                     if (subject.endsWith("/"))
790                     {
791                         // class directory
792                         File cd = new File(fixPath(file));
793                         String d = cd.getCanonicalPath();
794                         boolean added = addClasspathComponent(options,d);
795                         debug((added?"  CLASSPATH+=":"  !") + d);
796                         continue;
797                     }
798 
799                     // Add XML configuration
800                     if (subject.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
801                     {
802                         // Config file
803                         File f = new File(fixPath(file));
804                         if (f.exists())
805                             _xml.add(f.getCanonicalPath());
806                         debug("  ARGS+=" + f);
807                         continue;
808                     }
809 
810                     // Set the main class to execute (overrides any previously set)
811                     if (subject.toLowerCase(Locale.ENGLISH).endsWith(".class"))
812                     {
813                         // Class
814                         String cn = expand(subject.substring(0,subject.length() - 6));
815                         if (cn != null && cn.length() > 0)
816                         {
817                             debug("  CLASS=" + cn);
818                             _classname = cn;
819                         }
820                         continue;
821                     }
822 
823                     // Add raw classpath entry
824                     if (subject.toLowerCase(Locale.ENGLISH).endsWith(".path"))
825                     {
826                         // classpath (jetty.class.path?) to add to runtime classpath
827                         String cn = expand(subject.substring(0,subject.length() - 5));
828                         if (cn != null && cn.length() > 0)
829                         {
830                             debug("  PATH=" + cn);
831                             addClasspathPath(options,cn);
832                         }
833                         continue;
834                     }
835 
836                     // single JAR file
837                     File f = new File(fixPath(file));
838                     if (f.exists())
839                     {
840                         String d = f.getCanonicalPath();
841                         boolean added = addClasspathComponent(options,d);
842                         if (!added)
843                         {
844                             added = addClasspathPath(options,expand(subject));
845                         }
846                         debug((added?"  CLASSPATH+=":"  !") + d);
847                     }
848                 }
849                 catch (Exception e)
850                 {
851                     System.err.println("on line: '" + line + "'");
852                     e.printStackTrace();
853                 }
854             }
855         }
856         finally
857         {
858             close(buf);
859         }
860     }
861 
862     private List<String> processDynamicSectionIdentifier(String dynamicPathId,List<String> sections) throws IOException
863     {
864         String rawPath;
865         boolean deep;
866         
867         if (dynamicPathId.endsWith("/*"))
868         {
869             deep=false;
870             rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 1));
871         }
872         else if (dynamicPathId.endsWith("/**"))
873         {
874             deep=true;
875             rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 2));
876         }
877         else 
878         {
879             String msg = "Illegal dynamic path [" + dynamicPathId + "]";
880             throw new IOException(msg);
881         }
882         
883         File parentDir = new File(expand(rawPath));
884         if (!parentDir.exists())
885             return sections;
886         debug("dynamic: " + parentDir);
887 
888         File dirs[] = parentDir.listFiles(new FileFilter()
889         {
890             public boolean accept(File path)
891             {
892                 return path.isDirectory();
893             }
894         });
895 
896         List<String> dyn_sections = new ArrayList<String>();
897         List<String> super_sections = new ArrayList<String>();
898         if (sections!=null)
899             super_sections.addAll(sections);
900         
901         for (File dir : dirs)
902         {
903             String id = dir.getName();
904             if (!_classpaths.keySet().contains(id))
905                 _classpaths.put(id, new Classpath());
906             
907             dyn_sections.clear();
908             if (sections!=null)
909                 dyn_sections.addAll(sections);
910             dyn_sections.add(id);
911             super_sections.add(id);
912             debug("dynamic: " + dyn_sections);
913             addJars(dyn_sections,dir,deep);
914         }
915         
916         return super_sections;
917     }
918 
919     private String fixPath(String path)
920     {
921         return path.replace('/',File.separatorChar);
922     }
923 
924     public void parse(URL url) throws IOException
925     {
926         InputStream stream = null;
927         InputStreamReader reader = null;
928         try
929         {
930             stream = url.openStream();
931             reader = new InputStreamReader(stream);
932             parse(reader);
933         }
934         finally
935         {
936             close(reader);
937             close(stream);
938         }
939     }
940 
941     public void setArgCount(int argCount)
942     {
943         this.argCount = argCount;
944     }
945 
946     public void setProperty(String name, String value)
947     {
948         if (name.equals("DEBUG"))
949         {
950             DEBUG = Boolean.parseBoolean(value);
951             if (DEBUG)
952             {
953                 System.setProperty("org.eclipse.jetty.util.log.stderr.DEBUG","true");
954                 System.setProperty("org.eclipse.jetty.start.DEBUG","true");
955             }
956         }
957         if (name.equals("OPTIONS"))
958         {
959             _activeOptions.clear();
960             String ids[] = value.split(",");
961             for (String id : ids)
962             {
963                 addActiveOption(id);
964             }
965         }
966         __properties.put(name,value);
967     }
968 
969     public void addActiveOption(String option)
970     {
971         _activeOptions.add(option); 
972         __properties.put("OPTIONS",join(_activeOptions,","));
973     }
974 
975     public Set<String> getActiveOptions()
976     {
977         return _activeOptions;
978     }
979 
980     public void removeActiveOption(String option)
981     {
982         _activeOptions.remove(option);
983         __properties.put("OPTIONS",join(_activeOptions,","));
984     }
985     
986     private String join(Collection<?> coll, String delim)
987     {
988         StringBuffer buf = new StringBuffer();
989         Iterator<?> i = coll.iterator();
990         boolean hasNext = i.hasNext();
991         while (hasNext)
992         {
993             buf.append(String.valueOf(i.next()));
994             hasNext = i.hasNext();
995             if (hasNext)
996                 buf.append(delim);
997         }
998 
999         return buf.toString();
1000     }
1001 
1002 }