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