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.addon;
017    
018    import org.ini4j.Config;
019    import org.ini4j.Ini;
020    import org.ini4j.IniHandler;
021    import org.ini4j.InvalidIniFormatException;
022    
023    import org.ini4j.spi.Warnings;
024    
025    import java.io.File;
026    import java.io.FileReader;
027    import java.io.FileWriter;
028    import java.io.IOException;
029    import java.io.InputStream;
030    import java.io.OutputStream;
031    import java.io.Reader;
032    import java.io.Writer;
033    
034    import java.net.URL;
035    
036    import java.util.ArrayList;
037    import java.util.Collections;
038    import java.util.HashMap;
039    import java.util.List;
040    import java.util.Map;
041    import java.util.regex.Matcher;
042    import java.util.regex.Pattern;
043    
044    public class ConfigParser
045    {
046        private PyIni _ini;
047    
048        @SuppressWarnings(Warnings.UNCHECKED)
049        public ConfigParser()
050        {
051            this(Collections.EMPTY_MAP);
052        }
053    
054        public ConfigParser(Map<String, String> defaults)
055        {
056            _ini = new PyIni(defaults);
057        }
058    
059        public boolean getBoolean(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
060        {
061            boolean ret;
062            String value = get(section, option);
063    
064            if ("1".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value))
065            {
066                ret = true;
067            }
068            else if ("0".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value))
069            {
070                ret = false;
071            }
072            else
073            {
074                throw new IllegalArgumentException(value);
075            }
076    
077            return ret;
078        }
079    
080        public double getDouble(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
081        {
082            return Double.parseDouble(get(section, option));
083        }
084    
085        public float getFloat(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
086        {
087            return Float.parseFloat(get(section, option));
088        }
089    
090        public int getInt(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
091        {
092            return Integer.parseInt(get(section, option));
093        }
094    
095        public long getLong(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
096        {
097            return Long.parseLong(get(section, option));
098        }
099    
100        public void addSection(String section) throws DuplicateSectionException
101        {
102            if (_ini.containsKey(section))
103            {
104                throw new DuplicateSectionException(section);
105            }
106            else if (PyIni.DEFAULT_SECTION_NAME.equalsIgnoreCase(section))
107            {
108                throw new IllegalArgumentException(section);
109            }
110    
111            _ini.add(section);
112        }
113    
114        public Map<String, String> defaults()
115        {
116            return _ini.getDefaults();
117        }
118    
119        @SuppressWarnings(Warnings.UNCHECKED)
120        public String get(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
121        {
122            return get(section, option, false, Collections.EMPTY_MAP);
123        }
124    
125        @SuppressWarnings(Warnings.UNCHECKED)
126        public String get(String section, String option, boolean raw) throws NoSectionException, NoOptionException, InterpolationException
127        {
128            return get(section, option, raw, Collections.EMPTY_MAP);
129        }
130    
131        public String get(String sectionName, String optionName, boolean raw, Map<String, String> variables) throws NoSectionException, NoOptionException,
132            InterpolationException
133        {
134            String value = requireOption(sectionName, optionName);
135    
136            if (!raw && (value != null) && (value.indexOf(PyIni.SUBST_CHAR) >= 0))
137            {
138                value = _ini.fetch(sectionName, optionName, variables);
139            }
140    
141            return value;
142        }
143    
144        public boolean hasOption(String sectionName, String optionName)
145        {
146            Ini.Section section = _ini.get(sectionName);
147    
148            return (section != null) && section.containsKey(optionName);
149        }
150    
151        public boolean hasSection(String sectionName)
152        {
153            return _ini.containsKey(sectionName);
154        }
155    
156        @SuppressWarnings(Warnings.UNCHECKED)
157        public List<Map.Entry<String, String>> items(String sectionName) throws NoSectionException, InterpolationMissingOptionException
158        {
159            return items(sectionName, false, Collections.EMPTY_MAP);
160        }
161    
162        @SuppressWarnings(Warnings.UNCHECKED)
163        public List<Map.Entry<String, String>> items(String sectionName, boolean raw) throws NoSectionException, 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 (InvalidIniFormatException 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 (InvalidIniFormatException 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 (InvalidIniFormatException 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 (InvalidIniFormatException 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 PyIni 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(), cause);
410            }
411        }
412    
413        protected static class PyIni extends Ini
414        {
415            private static final char SUBST_CHAR = '%';
416            private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\%\\(([^\\)]+)\\)");
417            private static final int G_OPTION = 1;
418            protected static final String DEFAULT_SECTION_NAME = "DEFAULT";
419            private final Map<String, String> _defaults;
420            private Ini.Section _defaultSection;
421    
422            public PyIni(Map<String, String> defaults)
423            {
424                _defaults = defaults;
425                Config cfg = getConfig().clone();
426    
427                cfg.setEscape(false);
428                cfg.setMultiOption(false);
429                cfg.setMultiSection(false);
430                cfg.setLowerCaseOption(true);
431                cfg.setLowerCaseSection(true);
432                super.setConfig(cfg);
433            }
434    
435            @Override public void setConfig(Config value)
436            {
437                assert true;
438            }
439    
440            public Map<String, String> getDefaults()
441            {
442                return _defaults;
443            }
444    
445            @Override public Section add(String name)
446            {
447                Section section;
448    
449                if (DEFAULT_SECTION_NAME.equalsIgnoreCase(name))
450                {
451                    if (_defaultSection == null)
452                    {
453                        _defaultSection = new Ini.Section(name);
454                    }
455    
456                    section = _defaultSection;
457                }
458                else
459                {
460                    section = super.add(name);
461                }
462    
463                return section;
464            }
465    
466            public String fetch(String sectionName, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException
467            {
468                return fetch(get(sectionName), optionName, variables);
469            }
470    
471            protected Ini.Section getDefaultSection()
472            {
473                return _defaultSection;
474            }
475    
476            protected String fetch(Ini.Section section, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException
477            {
478                String value = section.get(optionName);
479    
480                if ((value != null) && (value.indexOf(SUBST_CHAR) >= 0))
481                {
482                    StringBuilder buffer = new StringBuilder(value);
483    
484                    resolve(buffer, section, variables);
485                    value = buffer.toString();
486                }
487    
488                return value;
489            }
490    
491            protected void resolve(StringBuilder buffer, Ini.Section owner, Map<String, String> vars) throws InterpolationMissingOptionException
492            {
493                Matcher m = EXPRESSION.matcher(buffer);
494    
495                while (m.find())
496                {
497                    String optionName = m.group(G_OPTION);
498                    String value = owner.get(optionName);
499    
500                    if (value == null)
501                    {
502                        value = vars.get(optionName);
503                    }
504    
505                    if (value == null)
506                    {
507                        value = _defaults.get(optionName);
508                    }
509    
510                    if ((value == null) && (_defaultSection != null))
511                    {
512                        value = _defaultSection.get(optionName);
513                    }
514    
515                    if (value == null)
516                    {
517                        throw new InterpolationMissingOptionException(optionName);
518                    }
519    
520                    buffer.replace(m.start(), m.end(), value);
521                    m.reset(buffer);
522                }
523            }
524    
525            @Override protected void store(IniHandler formatter) throws IOException
526            {
527                formatter.startIni();
528                if (_defaultSection != null)
529                {
530                    store(formatter, _defaultSection);
531                }
532    
533                for (Ini.Section s : values())
534                {
535                    store(formatter, s);
536                }
537    
538                formatter.endIni();
539            }
540    
541            protected void store(IniHandler formatter, Section section) throws IOException
542            {
543                formatter.startSection(section.getName());
544                for (String name : section.keySet())
545                {
546                    formatter.handleOption(name, section.get(name));
547                }
548    
549                formatter.endSection();
550            }
551        }
552    }