001    /*
002     * Copyright 2005,2009 Ivan SZKIBA
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.ini4j;
017    
018    import org.ini4j.spi.IniHandler;
019    import org.ini4j.spi.Warnings;
020    
021    import java.io.File;
022    import java.io.FileReader;
023    import java.io.FileWriter;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.OutputStream;
027    import java.io.Reader;
028    import java.io.Serializable;
029    import java.io.Writer;
030    
031    import java.net.URL;
032    
033    import java.util.ArrayList;
034    import java.util.Collections;
035    import java.util.HashMap;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.regex.Matcher;
039    import java.util.regex.Pattern;
040    
041    public class ConfigParser implements Serializable
042    {
043        private static final long serialVersionUID = 9118857036229164353L;
044        private PyIni _ini;
045    
046        @SuppressWarnings(Warnings.UNCHECKED)
047        public ConfigParser()
048        {
049            this(Collections.EMPTY_MAP);
050        }
051    
052        public ConfigParser(Map<String, String> defaults)
053        {
054            _ini = new PyIni(defaults);
055        }
056    
057        public boolean getBoolean(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
058        {
059            boolean ret;
060            String value = get(section, option);
061    
062            if ("1".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value))
063            {
064                ret = true;
065            }
066            else if ("0".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value))
067            {
068                ret = false;
069            }
070            else
071            {
072                throw new IllegalArgumentException(value);
073            }
074    
075            return ret;
076        }
077    
078        public double getDouble(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
079        {
080            return Double.parseDouble(get(section, option));
081        }
082    
083        public float getFloat(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
084        {
085            return Float.parseFloat(get(section, option));
086        }
087    
088        public int getInt(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
089        {
090            return Integer.parseInt(get(section, option));
091        }
092    
093        public long getLong(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
094        {
095            return Long.parseLong(get(section, option));
096        }
097    
098        public void addSection(String section) throws DuplicateSectionException
099        {
100            if (_ini.containsKey(section))
101            {
102                throw new DuplicateSectionException(section);
103            }
104            else if (PyIni.DEFAULT_SECTION_NAME.equalsIgnoreCase(section))
105            {
106                throw new IllegalArgumentException(section);
107            }
108    
109            _ini.add(section);
110        }
111    
112        public Map<String, String> defaults()
113        {
114            return _ini.getDefaults();
115        }
116    
117        @SuppressWarnings(Warnings.UNCHECKED)
118        public String get(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
119        {
120            return get(section, option, false, Collections.EMPTY_MAP);
121        }
122    
123        @SuppressWarnings(Warnings.UNCHECKED)
124        public String get(String section, String option, boolean raw) throws NoSectionException, NoOptionException, InterpolationException
125        {
126            return get(section, option, raw, Collections.EMPTY_MAP);
127        }
128    
129        public String get(String sectionName, String optionName, boolean raw, Map<String, String> variables) throws NoSectionException, NoOptionException,
130            InterpolationException
131        {
132            String value = requireOption(sectionName, optionName);
133    
134            if (!raw && (value != null) && (value.indexOf(PyIni.SUBST_CHAR) >= 0))
135            {
136                value = _ini.fetch(sectionName, optionName, variables);
137            }
138    
139            return value;
140        }
141    
142        public boolean hasOption(String sectionName, String optionName)
143        {
144            Ini.Section section = _ini.get(sectionName);
145    
146            return (section != null) && section.containsKey(optionName);
147        }
148    
149        public boolean hasSection(String sectionName)
150        {
151            return _ini.containsKey(sectionName);
152        }
153    
154        @SuppressWarnings(Warnings.UNCHECKED)
155        public List<Map.Entry<String, String>> items(String sectionName) throws NoSectionException, InterpolationMissingOptionException
156        {
157            return items(sectionName, false, Collections.EMPTY_MAP);
158        }
159    
160        @SuppressWarnings(Warnings.UNCHECKED)
161        public List<Map.Entry<String, String>> items(String sectionName, boolean raw) throws NoSectionException, InterpolationMissingOptionException
162        {
163            return items(sectionName, raw, Collections.EMPTY_MAP);
164        }
165    
166        public List<Map.Entry<String, String>> items(String sectionName, boolean raw, Map<String, String> variables) throws NoSectionException,
167            InterpolationMissingOptionException
168        {
169            Ini.Section section = requireSection(sectionName);
170            Map<String, String> ret;
171    
172            if (raw)
173            {
174                ret = new HashMap<String, String>(section);
175            }
176            else
177            {
178                ret = new HashMap<String, String>();
179                for (String key : section.keySet())
180                {
181                    ret.put(key, _ini.fetch(section, key, variables));
182                }
183            }
184    
185            return new ArrayList<Map.Entry<String, String>>(ret.entrySet());
186        }
187    
188        public List<String> options(String sectionName) throws NoSectionException
189        {
190            requireSection(sectionName);
191    
192            return new ArrayList<String>(_ini.get(sectionName).keySet());
193        }
194    
195        public void read(String... filenames) throws IOException, ParsingException
196        {
197            for (String filename : filenames)
198            {
199                read(new File(filename));
200            }
201        }
202    
203        public void read(Reader reader) throws IOException, ParsingException
204        {
205            try
206            {
207                _ini.load(reader);
208            }
209            catch (InvalidFileFormatException x)
210            {
211                throw new ParsingException(x);
212            }
213        }
214    
215        public void read(URL url) throws IOException, ParsingException
216        {
217            try
218            {
219                _ini.load(url);
220            }
221            catch (InvalidFileFormatException x)
222            {
223                throw new ParsingException(x);
224            }
225        }
226    
227        public void read(File file) throws IOException, ParsingException
228        {
229            try
230            {
231                _ini.load(new FileReader(file));
232            }
233            catch (InvalidFileFormatException x)
234            {
235                throw new ParsingException(x);
236            }
237        }
238    
239        public void read(InputStream stream) throws IOException, ParsingException
240        {
241            try
242            {
243                _ini.load(stream);
244            }
245            catch (InvalidFileFormatException x)
246            {
247                throw new ParsingException(x);
248            }
249        }
250    
251        public boolean removeOption(String sectionName, String optionName) throws NoSectionException
252        {
253            Ini.Section section = requireSection(sectionName);
254            boolean ret = section.containsKey(optionName);
255    
256            section.remove(optionName);
257    
258            return ret;
259        }
260    
261        public boolean removeSection(String sectionName)
262        {
263            boolean ret = _ini.containsKey(sectionName);
264    
265            _ini.remove(sectionName);
266    
267            return ret;
268        }
269    
270        public List<String> sections()
271        {
272            return new ArrayList<String>(_ini.keySet());
273        }
274    
275        public void set(String sectionName, String optionName, Object value) throws NoSectionException
276        {
277            Ini.Section section = requireSection(sectionName);
278    
279            if (value == null)
280            {
281                section.remove(optionName);
282            }
283            else
284            {
285                section.put(optionName, value.toString());
286            }
287        }
288    
289        public void write(Writer writer) throws IOException
290        {
291            _ini.store(writer);
292        }
293    
294        public void write(OutputStream stream) throws IOException
295        {
296            _ini.store(stream);
297        }
298    
299        public void write(File file) throws IOException
300        {
301            _ini.store(new FileWriter(file));
302        }
303    
304        protected Ini getIni()
305        {
306            return _ini;
307        }
308    
309        private String requireOption(String sectionName, String optionName) throws NoSectionException, NoOptionException
310        {
311            Ini.Section section = requireSection(sectionName);
312            String option = section.get(optionName);
313    
314            if (option == null)
315            {
316                throw new NoOptionException(optionName);
317            }
318    
319            return option;
320        }
321    
322        private Ini.Section requireSection(String sectionName) throws NoSectionException
323        {
324            Ini.Section section = _ini.get(sectionName);
325    
326            if (section == null)
327            {
328                throw new NoSectionException(sectionName);
329            }
330    
331            return section;
332        }
333    
334        public static class ConfigParserException extends Exception
335        {
336    
337            /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = -6845546313519392093L;
338    
339            public ConfigParserException(String message)
340            {
341                super(message);
342            }
343        }
344    
345        public static final class DuplicateSectionException extends ConfigParserException
346        {
347    
348            /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = -5244008445735700699L;
349    
350            private DuplicateSectionException(String message)
351            {
352                super(message);
353            }
354        }
355    
356        public static class InterpolationException extends ConfigParserException
357        {
358    
359            /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 8924443303158546939L;
360    
361            protected InterpolationException(String message)
362            {
363                super(message);
364            }
365        }
366    
367        public static final class InterpolationMissingOptionException extends InterpolationException
368        {
369    
370            /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 2903136975820447879L;
371    
372            private InterpolationMissingOptionException(String message)
373            {
374                super(message);
375            }
376        }
377    
378        public static final class NoOptionException extends ConfigParserException
379        {
380    
381            /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 8460082078809425858L;
382    
383            private NoOptionException(String message)
384            {
385                super(message);
386            }
387        }
388    
389        public static final class NoSectionException extends ConfigParserException
390        {
391    
392            /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 8553627727493146118L;
393    
394            private NoSectionException(String message)
395            {
396                super(message);
397            }
398        }
399    
400        public static final class ParsingException extends IOException
401        {
402    
403            /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = -5395990242007205038L;
404    
405            private ParsingException(Throwable cause)
406            {
407                super(cause.getMessage(), cause);
408            }
409        }
410    
411        static class PyIni extends Ini
412        {
413            private static final char SUBST_CHAR = '%';
414            private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\%\\(([^\\)]+)\\)");
415            private static final int G_OPTION = 1;
416            protected static final String DEFAULT_SECTION_NAME = "DEFAULT";
417            private static final long serialVersionUID = -7152857626328996122L;
418            private final Map<String, String> _defaults;
419            private Ini.Section _defaultSection;
420    
421            public PyIni(Map<String, String> defaults)
422            {
423                _defaults = defaults;
424                Config cfg = getConfig().clone();
425    
426                cfg.setEscape(false);
427                cfg.setMultiOption(false);
428                cfg.setMultiSection(false);
429                cfg.setLowerCaseOption(true);
430                cfg.setLowerCaseSection(true);
431                super.setConfig(cfg);
432            }
433    
434            @Override public void setConfig(Config value)
435            {
436                assert true;
437            }
438    
439            public Map<String, String> getDefaults()
440            {
441                return _defaults;
442            }
443    
444            @Override public Section add(String name)
445            {
446                Section section;
447    
448                if (DEFAULT_SECTION_NAME.equalsIgnoreCase(name))
449                {
450                    if (_defaultSection == null)
451                    {
452                        _defaultSection = newSection(name);
453                    }
454    
455                    section = _defaultSection;
456                }
457                else
458                {
459                    section = super.add(name);
460                }
461    
462                return section;
463            }
464    
465            public String fetch(String sectionName, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException
466            {
467                return fetch(get(sectionName), optionName, variables);
468            }
469    
470            protected Ini.Section getDefaultSection()
471            {
472                return _defaultSection;
473            }
474    
475            protected String fetch(Ini.Section section, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException
476            {
477                String value = section.get(optionName);
478    
479                if ((value != null) && (value.indexOf(SUBST_CHAR) >= 0))
480                {
481                    StringBuilder buffer = new StringBuilder(value);
482    
483                    resolve(buffer, section, variables);
484                    value = buffer.toString();
485                }
486    
487                return value;
488            }
489    
490            protected void resolve(StringBuilder buffer, Ini.Section owner, Map<String, String> vars) throws InterpolationMissingOptionException
491            {
492                Matcher m = EXPRESSION.matcher(buffer);
493    
494                while (m.find())
495                {
496                    String optionName = m.group(G_OPTION);
497                    String value = owner.get(optionName);
498    
499                    if (value == null)
500                    {
501                        value = vars.get(optionName);
502                    }
503    
504                    if (value == null)
505                    {
506                        value = _defaults.get(optionName);
507                    }
508    
509                    if ((value == null) && (_defaultSection != null))
510                    {
511                        value = _defaultSection.get(optionName);
512                    }
513    
514                    if (value == null)
515                    {
516                        throw new InterpolationMissingOptionException(optionName);
517                    }
518    
519                    buffer.replace(m.start(), m.end(), value);
520                    m.reset(buffer);
521                }
522            }
523    
524            @Override protected void store(IniHandler formatter)
525            {
526                formatter.startIni();
527                if (_defaultSection != null)
528                {
529                    store(formatter, _defaultSection);
530                }
531    
532                for (Ini.Section s : values())
533                {
534                    store(formatter, s);
535                }
536    
537                formatter.endIni();
538            }
539    
540            @Override protected void store(IniHandler formatter, Section section)
541            {
542                formatter.startSection(section.getName());
543                for (String name : section.keySet())
544                {
545                    formatter.handleOption(name, section.get(name));
546                }
547    
548                formatter.endSection();
549            }
550        }
551    }