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 */
016package org.ini4j;
017
018import org.ini4j.spi.IniHandler;
019import org.ini4j.spi.Warnings;
020
021import java.io.File;
022import java.io.FileReader;
023import java.io.FileWriter;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.OutputStream;
027import java.io.Reader;
028import java.io.Serializable;
029import java.io.Writer;
030
031import java.net.URL;
032
033import java.util.ArrayList;
034import java.util.Collections;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038import java.util.regex.Matcher;
039import java.util.regex.Pattern;
040
041public 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}