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.AbstractBeanInvocationHandler;
019    import org.ini4j.spi.IniFormatter;
020    import org.ini4j.spi.XMLFormatter;
021    
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.InputStreamReader;
025    import java.io.OutputStream;
026    import java.io.Reader;
027    import java.io.Writer;
028    
029    import java.lang.reflect.Array;
030    import java.lang.reflect.Proxy;
031    
032    import java.net.URL;
033    
034    import java.util.HashMap;
035    import java.util.Map;
036    import java.util.regex.Matcher;
037    import java.util.regex.Pattern;
038    
039    public class Ini extends MultiMapImpl<String, Ini.Section>
040    {
041        private static final String SECTION_SYSTEM_PROPERTIES = "@prop";
042        private static final String SECTION_ENVIRONMENT = "@env";
043        private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\$\\{(([^\\[]+)(\\[([0-9]+)\\])?/)?([^\\[]+)(\\[(([0-9]+))\\])?\\}");
044        private static final int G_SECTION = 2;
045        private static final int G_SECTION_IDX = 4;
046        private static final int G_OPTION = 5;
047        private static final int G_OPTION_IDX = 7;
048        private Map<Class, Object> _beans;
049        private Config _config = Config.getGlobal();
050    
051        public Ini()
052        {
053            assert true;
054        }
055    
056        public Ini(Reader input) throws IOException, InvalidIniFormatException
057        {
058            this();
059            load(input);
060        }
061    
062        public Ini(InputStream input) throws IOException, InvalidIniFormatException
063        {
064            this();
065            load(input);
066        }
067    
068        public Ini(URL input) throws IOException, InvalidIniFormatException
069        {
070            this();
071            load(input);
072        }
073    
074        public void setConfig(Config value)
075        {
076            _config = value;
077        }
078    
079        public Section add(String name)
080        {
081            Section s = new Section(name);
082    
083            if (getConfig().isMultiSection())
084            {
085                add(name, s);
086            }
087            else
088            {
089                put(name, s);
090            }
091    
092            return s;
093        }
094    
095        public <T> T as(Class<T> clazz)
096        {
097            return clazz.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { clazz }, new BeanInvocationHandler()));
098        }
099    
100        public void load(InputStream input) throws IOException, InvalidIniFormatException
101        {
102            IniParser.newInstance(getConfig()).parse(input, new Builder());
103        }
104    
105        public void load(Reader input) throws IOException, InvalidIniFormatException
106        {
107            IniParser.newInstance(getConfig()).parse(input, new Builder());
108        }
109    
110        public void load(URL input) throws IOException, InvalidIniFormatException
111        {
112            IniParser.newInstance(getConfig()).parse(input, new Builder());
113        }
114    
115        public void loadFromXML(InputStream input) throws IOException, InvalidIniFormatException
116        {
117            loadFromXML(new InputStreamReader(input));
118        }
119    
120        public void loadFromXML(Reader input) throws IOException, InvalidIniFormatException
121        {
122            Builder builder = new Builder();
123    
124            IniParser.newInstance(getConfig()).parseXML(input, builder);
125        }
126    
127        public void loadFromXML(URL input) throws IOException, InvalidIniFormatException
128        {
129            Builder builder = new Builder();
130    
131            IniParser.newInstance(getConfig()).parseXML(input, builder);
132        }
133    
134        public Section remove(Section section)
135        {
136            return remove((Object) section.getName());
137        }
138    
139        public void store(OutputStream output) throws IOException
140        {
141            store(IniFormatter.newInstance(output, getConfig()));
142        }
143    
144        public void store(Writer output) throws IOException
145        {
146            store(IniFormatter.newInstance(output, getConfig()));
147        }
148    
149        public void storeToXML(OutputStream output) throws IOException
150        {
151            store(XMLFormatter.newInstance(output));
152        }
153    
154        public void storeToXML(Writer output) throws IOException
155        {
156            store(XMLFormatter.newInstance(output));
157        }
158    
159        @Deprecated public synchronized <T> T to(Class<T> clazz)
160        {
161            Object bean = null;
162    
163            if (_beans == null)
164            {
165                _beans = new HashMap<Class, Object>();
166            }
167            else
168            {
169                bean = _beans.get(clazz);
170            }
171    
172            if (bean == null)
173            {
174                bean = as(clazz);
175                _beans.put(clazz, bean);
176            }
177    
178            return clazz.cast(bean);
179        }
180    
181        protected Config getConfig()
182        {
183            return _config;
184        }
185    
186        protected void resolve(StringBuilder buffer, Section owner)
187        {
188            Matcher m = EXPRESSION.matcher(buffer);
189    
190            while (m.find())
191            {
192                String sectionName = m.group(G_SECTION);
193                String optionName = m.group(G_OPTION);
194                int optionIndex = parseOptionIndex(m);
195                Section section = parseSection(m, owner);
196                String value = null;
197    
198                if (SECTION_ENVIRONMENT.equals(sectionName))
199                {
200                    value = System.getenv(optionName);
201                }
202                else if (SECTION_SYSTEM_PROPERTIES.equals(sectionName))
203                {
204                    value = System.getProperty(optionName);
205                }
206                else if (section != null)
207                {
208                    value = (optionIndex == -1) ? section.fetch(optionName) : section.fetch(optionName, optionIndex);
209                }
210    
211                if (value != null)
212                {
213                    buffer.replace(m.start(), m.end(), value);
214                    m.reset(buffer);
215                }
216            }
217        }
218    
219        protected void store(IniHandler formatter) throws IOException
220        {
221            formatter.startIni();
222            for (Ini.Section s : values())
223            {
224                formatter.startSection(s.getName());
225                for (String name : s.keySet())
226                {
227                    int n = getConfig().isMultiOption() ? s.length(name) : 1;
228    
229                    for (int i = 0; i < n; i++)
230                    {
231                        formatter.handleOption(name, s.get(name, i));
232                    }
233                }
234    
235                formatter.endSection();
236            }
237    
238            formatter.endIni();
239        }
240    
241        private int parseOptionIndex(Matcher m)
242        {
243            return (m.group(G_OPTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_OPTION_IDX));
244        }
245    
246        private Section parseSection(Matcher m, Section owner)
247        {
248            String sectionName = m.group(G_SECTION);
249            int sectionIndex = parseSectionIndex(m);
250    
251            return (sectionName == null) ? owner : ((sectionIndex == -1) ? get(sectionName) : get(sectionName, sectionIndex));
252        }
253    
254        private int parseSectionIndex(Matcher m)
255        {
256            return (m.group(G_SECTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_SECTION_IDX));
257        }
258    
259        public class Section extends OptionMapImpl
260        {
261            private Map<Class, Object> _beans;
262            private final String _name;
263    
264            public Section(String name)
265            {
266                super();
267                _name = name;
268            }
269    
270            public String getName()
271            {
272                return _name;
273            }
274    
275            @Deprecated public synchronized <T> T to(Class<T> clazz)
276            {
277                Object bean = null;
278    
279                if (_beans == null)
280                {
281                    _beans = new HashMap<Class, Object>();
282                }
283                else
284                {
285                    bean = _beans.get(clazz);
286                }
287    
288                if (bean == null)
289                {
290                    bean = as(clazz);
291                    _beans.put(clazz, bean);
292                }
293    
294                return clazz.cast(bean);
295            }
296    
297            @Override protected void resolve(StringBuilder buffer)
298            {
299                Ini.this.resolve(buffer, this);
300            }
301        }
302    
303        private class BeanInvocationHandler extends AbstractBeanInvocationHandler
304        {
305            private final MultiMap<String, Object> _sectionBeans = new MultiMapImpl<String, Object>();
306    
307            @Override protected Object getPropertySpi(String property, Class<?> clazz)
308            {
309                Object o = null;
310    
311                if (clazz.isArray())
312                {
313                    if (!_sectionBeans.containsKey(property) && containsKey(property))
314                    {
315                        for (int i = 0; i < length(property); i++)
316                        {
317                            _sectionBeans.add(property, get(property, i).as(clazz.getComponentType()));
318                        }
319                    }
320    
321                    if (_sectionBeans.containsKey(property))
322                    {
323                        o = Array.newInstance(clazz.getComponentType(), _sectionBeans.length(property));
324                        for (int i = 0; i < _sectionBeans.length(property); i++)
325                        {
326                            Array.set(o, i, _sectionBeans.get(property, i));
327                        }
328                    }
329                }
330                else
331                {
332                    o = _sectionBeans.get(property);
333                    if (o == null)
334                    {
335                        Section section = get(property);
336    
337                        if (section != null)
338                        {
339                            o = section.as(clazz);
340                            _sectionBeans.put(property, o);
341                        }
342                    }
343                }
344    
345                return o;
346            }
347    
348            @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
349            {
350                remove(property);
351                if (value != null)
352                {
353                    if (clazz.isArray())
354                    {
355                        for (int i = 0; i < Array.getLength(value); i++)
356                        {
357                            Section sec = add(property);
358    
359                            sec.from(Array.get(value, i));
360                        }
361                    }
362                    else
363                    {
364                        Section sec = add(property);
365    
366                        sec.from(value);
367                    }
368                }
369            }
370    
371            @Override protected boolean hasPropertySpi(String property)
372            {
373                return containsKey(property);
374            }
375        }
376    
377        private class Builder implements IniHandler
378        {
379            private Section _currentSection;
380    
381            public void endIni()
382            {
383                assert true;
384            }
385    
386            @Override public void endSection()
387            {
388                _currentSection = null;
389            }
390    
391            @Override public void handleOption(String name, String value)
392            {
393                if (getConfig().isMultiOption())
394                {
395                    _currentSection.add(name, value);
396                }
397                else
398                {
399                    _currentSection.put(name, value);
400                }
401            }
402    
403            public void startIni()
404            {
405                assert true;
406            }
407    
408            @Override public void startSection(String sectionName)
409            {
410                if (getConfig().isMultiSection())
411                {
412                    _currentSection = add(sectionName);
413                }
414                else
415                {
416                    Section s = get(sectionName);
417    
418                    _currentSection = (s == null) ? add(sectionName) : s;
419                }
420            }
421        }
422    }