001    /*
002     * Copyright 2005 [ini4j] Development Team
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 java.io.*;
019    import java.net.URL;
020    import java.util.*;
021    import java.lang.reflect.*;
022    
023    import org.xml.sax.*;
024    import org.xml.sax.helpers.*;
025    import javax.xml.parsers.*;
026    
027    public class Ini extends LinkedHashMap<String,Ini.Section>
028    {
029        private static final String OPERATOR = " " + IniParser.OPERATOR + " ";
030        private static final char SUBST_CHAR = '$';
031        private static final String SUBST_BEGIN = SUBST_CHAR + "{";
032        private static final int SUBST_BEGIN_LEN = SUBST_BEGIN.length();
033        private static final String SUBST_END = "}";
034        private static final int SUBST_END_LEN = SUBST_END.length();
035        private static final char SUBST_ESCAPE = '\\';
036        private static final char SUBST_SEPARATOR = '/';
037        private static final String SUBST_PROPERTY = "@prop";
038        private static final String SUBST_ENVIRONMENT = "@env";
039        
040        private Map<Class,Object> _beans;
041        
042        public class Section extends LinkedHashMap<String,String>
043        {
044            private String _name;
045            private Map<Class,Object> _beans;
046            
047            class BeanInvocationHandler extends AbstractBeanInvocationHandler
048            {
049                protected Object getPropertySpi(String property, Class<?> clazz)
050                {
051                    return fetch(property);
052                }
053                
054                protected void setPropertySpi(String property, Object value, Class<?> clazz)
055                {
056                    put(property, value.toString());
057                }
058                
059                protected boolean hasPropertySpi(String property)
060                {
061                    return containsKey(property);
062                }
063            }
064            
065            public Section(String name)
066            {
067                super();
068                _name = name;
069            }
070            
071            public String getName()
072            {
073                return _name;
074            }
075            
076            public synchronized <T> T to(Class<T> clazz)
077            {
078                Object bean;
079                
080                if ( _beans == null )
081                {
082                    _beans = new HashMap<Class,Object>();
083                    bean = null;
084                }
085                else
086                {
087                    bean = _beans.get(clazz);
088                }
089                
090                if ( bean == null )
091                {
092                    bean = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {clazz}, new BeanInvocationHandler());
093                    _beans.put(clazz, bean);
094                }
095                
096                return clazz.cast(bean);
097            }
098            
099            public String fetch(Object key)
100            {
101                String value = get(key);
102                
103                if ( (value != null) && (value.indexOf(SUBST_CHAR) >= 0) )
104                {
105                    StringBuilder buffer = new StringBuilder(value);
106                    resolve(buffer, this);
107                    value = buffer.toString();
108                }
109                return value;
110            }
111        }
112        
113        class BeanInvocationHandler extends AbstractBeanInvocationHandler
114        {
115            private Map<String,Object> _sectionBeans = new HashMap<String, Object>();
116            
117            protected Object getPropertySpi(String property, Class<?> clazz)
118            {
119                Object o = _sectionBeans.get(property);
120                
121                if ( o == null )
122                {
123                    Section section = get(property);
124                    
125                    if ( section != null )
126                    {
127                        o = section.to(clazz);
128                        _sectionBeans.put(property, o);
129                    }
130                }
131                
132                return o;
133            }
134            
135            protected void setPropertySpi(String property, Object value, Class<?> clazz)
136            {
137                throw new UnsupportedOperationException("read only bean");
138            }
139            
140            protected boolean hasPropertySpi(String property)
141            {
142                return false;
143            }
144        }
145        
146        class Builder implements IniHandler
147        {
148            private Section currentSection;
149            
150            public void startIni()
151            {
152                ;
153            }
154            
155            public void endIni()
156            {
157                ;
158            }
159            
160            public void startSection(String sectionName)
161            {
162                Section s = get(sectionName);
163                currentSection = (s != null) ? s : add(sectionName);
164            }
165            
166            public void endSection()
167            {
168                currentSection = null;
169            }
170            
171            public void handleOption(String name, String value)
172            {
173                currentSection.put(name, value);
174            }
175        }
176        
177        public Ini()
178        {
179            ;
180        }
181        
182        public Ini(Reader input) throws IOException, InvalidIniFormatException
183        {
184            this();
185            load(input);
186        }
187        
188        public Ini(InputStream input) throws IOException, InvalidIniFormatException
189        {
190            this();
191            load(input);
192        }
193        
194        public Ini(URL input) throws IOException, InvalidIniFormatException
195        {
196            this();
197            load(input);
198        }
199        
200        public Section add(String name)
201        {
202            Section s = new Section(name);
203            put(name, s);
204            return s;
205        }
206        
207        public Section remove(Section section)
208        {
209            return remove((Object)section.getName() );
210        }
211        
212        public void store(OutputStream output) throws IOException
213        {
214            store(new OutputStreamWriter(output));
215        }
216        
217        public void store(Writer output) throws IOException
218        {
219            PrintWriter pr = new PrintWriter(output);
220            
221            for(Ini.Section s : values())
222            {
223                pr.print(IniParser.SECTION_BEGIN);
224                pr.print(Convert.escape(s.getName()));
225                pr.println(IniParser.SECTION_END);
226                
227                for(Map.Entry<String,String> e : s.entrySet())
228                {
229                    pr.print(Convert.escape(e.getKey()));
230                    pr.print(OPERATOR);
231                    pr.println(Convert.escape(e.getValue()));
232                }
233                
234                pr.println();
235            }
236            pr.flush();
237        }
238        
239        public void load(InputStream input) throws IOException, InvalidIniFormatException
240        {
241            load(new InputStreamReader(input));
242        }
243        
244        public void load(Reader input) throws IOException, InvalidIniFormatException
245        {
246            Builder builder = new Builder();
247            IniParser.newInstance().parse(input, builder);
248        }
249        
250        public void load(URL input) throws IOException, InvalidIniFormatException
251        {
252            Builder builder = new Builder();
253            IniParser.newInstance().parse(input, builder);
254        }
255        
256        public void storeToXML(OutputStream output) throws IOException
257        {
258            storeToXML(new OutputStreamWriter(output));
259        }
260        
261        public void storeToXML(Writer output) throws IOException
262        {
263            PrintWriter pr = new PrintWriter(output);
264            
265            pr.println("<ini version='1.0'>");
266            
267            for(Ini.Section s : values())
268            {
269                pr.print(" <section key='");
270                pr.print(s.getName());
271                pr.println("'>");
272                
273                for(Map.Entry<String,String> e : s.entrySet())
274                {
275                    pr.print("  <option key='");
276                    pr.print(e.getKey());
277                    pr.print("' value='");
278                    pr.print(e.getValue());
279                    pr.println("'/>");
280                }
281                
282                pr.println(" </section>");
283            }
284            
285            pr.println("</ini>");
286            pr.flush();
287        }
288        
289        public void loadFromXML(InputStream input) throws IOException, InvalidIniFormatException
290        {
291            loadFromXML(new InputStreamReader(input));
292        }
293        
294        public void loadFromXML(Reader input) throws IOException, InvalidIniFormatException
295        {
296            Builder builder = new Builder();
297            IniParser.newInstance().parseXML(input, builder);
298        }
299        
300        public void loadFromXML(URL input) throws IOException, InvalidIniFormatException
301        {
302            Builder builder = new Builder();
303            IniParser.newInstance().parseXML(input, builder);
304        }
305        
306        public <T> T to(Class<T> clazz)
307        {
308            Object bean;
309            
310            if ( _beans == null )
311            {
312                _beans = new HashMap<Class,Object>();
313                bean = null;
314            }
315            else
316            {
317                bean = _beans.get(clazz);
318            }
319            
320            if ( bean == null )
321            {
322                bean = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {clazz}, new BeanInvocationHandler());
323                _beans.put(clazz, bean);
324            }
325            
326            return clazz.cast(bean);
327        }
328        
329        protected void resolve(StringBuilder buffer, Section owner)
330        {
331            int begin = -1;
332            int end = -1;
333            
334            for(int i = buffer.indexOf(SUBST_BEGIN); (i>=0); i = buffer.indexOf(SUBST_BEGIN, i+1) )
335            {
336                if ( (i+2) > buffer.length() )
337                {
338                    break;
339                }
340                
341                if ( (i != 0) && (buffer.charAt(i-1) == SUBST_ESCAPE) )
342                {
343                    continue;
344                }
345                
346                begin = i;
347                
348                end = buffer.indexOf(SUBST_END, i);
349                
350                if ( end < 0 )
351                {
352                    break;
353                }
354                
355                if ( (begin >= 0) && (end > 0) )
356                {
357                    String var = buffer.substring(begin+SUBST_BEGIN_LEN,end);
358                    String group = null;
359                    int sep = var.indexOf(SUBST_SEPARATOR);
360                    String value = null;
361                    
362                    if ( sep > 0 )
363                    {
364                        group = var.substring(0,sep);
365                        var = var.substring(sep+1);
366                    }
367                    
368                    if ( var != null )
369                    {
370                        if ( group == null )
371                        {
372                            value = owner.fetch(var);
373                        }
374                        else if ( SUBST_ENVIRONMENT.equals(group))
375                        {
376                            value = System.getenv(var);
377                        }
378                        else if ( SUBST_PROPERTY.equals(group) )
379                        {
380                            value = System.getProperty(var);
381                        }
382                        else
383                        {
384                            owner = get(group);
385                            
386                            if ( owner != null )
387                            {
388                                value = owner.fetch(var);
389                            }
390                        }
391                    }
392                    
393                    if ( value != null )
394                    {
395                        buffer.replace(begin,end+SUBST_END_LEN, value);
396                    }
397                }
398            }
399        }
400        
401    }