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)
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    }