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.BeanTool;
020    import org.ini4j.spi.IniHandler;
021    
022    import java.lang.reflect.Array;
023    import java.lang.reflect.Proxy;
024    
025    import java.util.regex.Matcher;
026    import java.util.regex.Pattern;
027    
028    public class BasicProfile extends CommonMultiMap<String, Profile.Section> implements Profile
029    {
030        private static final String SECTION_SYSTEM_PROPERTIES = "@prop";
031        private static final String SECTION_ENVIRONMENT = "@env";
032        private static final Pattern EXPRESSION = Pattern.compile(
033                "(?<!\\\\)\\$\\{(([^\\[\\}]+)(\\[([0-9]+)\\])?/)?([^\\[^/\\}]+)(\\[(([0-9]+))\\])?\\}");
034        private static final int G_SECTION = 2;
035        private static final int G_SECTION_IDX = 4;
036        private static final int G_OPTION = 5;
037        private static final int G_OPTION_IDX = 7;
038        private static final long serialVersionUID = -1817521505004015256L;
039        private String _comment;
040        private final boolean _propertyFirstUpper;
041        private final boolean _treeMode;
042    
043        public BasicProfile()
044        {
045            this(false, false);
046        }
047    
048        public BasicProfile(boolean treeMode, boolean propertyFirstUpper)
049        {
050            _treeMode = treeMode;
051            _propertyFirstUpper = propertyFirstUpper;
052        }
053    
054        @Override public String getComment()
055        {
056            return _comment;
057        }
058    
059        @Override public void setComment(String value)
060        {
061            _comment = value;
062        }
063    
064        @Override public Section add(String name)
065        {
066            if (isTreeMode())
067            {
068                int idx = name.lastIndexOf(getPathSeparator());
069    
070                if (idx > 0)
071                {
072                    String parent = name.substring(0, idx);
073    
074                    if (!containsKey(parent))
075                    {
076                        add(parent);
077                    }
078                }
079            }
080    
081            Section section = newSection(name);
082    
083            add(name, section);
084    
085            return section;
086        }
087    
088        @Override public void add(String section, String option, Object value)
089        {
090            getOrAdd(section).add(option, value);
091        }
092    
093        @Override public <T> T as(Class<T> clazz)
094        {
095            return as(clazz, null);
096        }
097    
098        @Override public <T> T as(Class<T> clazz, String prefix)
099        {
100            return clazz.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { clazz },
101                        new BeanInvocationHandler(prefix)));
102        }
103    
104        @Override public String fetch(Object sectionName, Object optionName)
105        {
106            Section sec = get(sectionName);
107    
108            return (sec == null) ? null : sec.fetch(optionName);
109        }
110    
111        @Override public <T> T fetch(Object sectionName, Object optionName, Class<T> clazz)
112        {
113            Section sec = get(sectionName);
114    
115            return (sec == null) ? BeanTool.getInstance().zero(clazz) : sec.fetch(optionName, clazz);
116        }
117    
118        @Override public String get(Object sectionName, Object optionName)
119        {
120            Section sec = get(sectionName);
121    
122            return (sec == null) ? null : sec.get(optionName);
123        }
124    
125        @Override public <T> T get(Object sectionName, Object optionName, Class<T> clazz)
126        {
127            Section sec = get(sectionName);
128    
129            return (sec == null) ? BeanTool.getInstance().zero(clazz) : sec.get(optionName, clazz);
130        }
131    
132        @Override public String put(String sectionName, String optionName, Object value)
133        {
134            return getOrAdd(sectionName).put(optionName, value);
135        }
136    
137        @Override public Section remove(Section section)
138        {
139            return remove((Object) section.getName());
140        }
141    
142        @Override public String remove(Object sectionName, Object optionName)
143        {
144            Section sec = get(sectionName);
145    
146            return (sec == null) ? null : sec.remove(optionName);
147        }
148    
149        boolean isTreeMode()
150        {
151            return _treeMode;
152        }
153    
154        char getPathSeparator()
155        {
156            return PATH_SEPARATOR;
157        }
158    
159        boolean isPropertyFirstUpper()
160        {
161            return _propertyFirstUpper;
162        }
163    
164        Section newSection(String name)
165        {
166            return new BasicProfileSection(this, name);
167        }
168    
169        void resolve(StringBuilder buffer, Section owner)
170        {
171            Matcher m = EXPRESSION.matcher(buffer);
172    
173            while (m.find())
174            {
175                String sectionName = m.group(G_SECTION);
176                String optionName = m.group(G_OPTION);
177                int optionIndex = parseOptionIndex(m);
178                Section section = parseSection(m, owner);
179                String value = null;
180    
181                if (SECTION_ENVIRONMENT.equals(sectionName))
182                {
183                    value = Config.getEnvironment(optionName);
184                }
185                else if (SECTION_SYSTEM_PROPERTIES.equals(sectionName))
186                {
187                    value = Config.getSystemProperty(optionName);
188                }
189                else if (section != null)
190                {
191                    value = (optionIndex == -1) ? section.fetch(optionName) : section.fetch(optionName, optionIndex);
192                }
193    
194                if (value != null)
195                {
196                    buffer.replace(m.start(), m.end(), value);
197                    m.reset(buffer);
198                }
199            }
200        }
201    
202        void store(IniHandler formatter)
203        {
204            formatter.startIni();
205            store(formatter, getComment());
206            for (Ini.Section s : values())
207            {
208                store(formatter, s);
209            }
210    
211            formatter.endIni();
212        }
213    
214        void store(IniHandler formatter, Section s)
215        {
216            store(formatter, getComment(s.getName()));
217            formatter.startSection(s.getName());
218            for (String name : s.keySet())
219            {
220                store(formatter, s, name);
221            }
222    
223            formatter.endSection();
224        }
225    
226        void store(IniHandler formatter, String comment)
227        {
228            formatter.handleComment(comment);
229        }
230    
231        void store(IniHandler formatter, Section section, String option)
232        {
233            store(formatter, section.getComment(option));
234            int n = section.length(option);
235    
236            for (int i = 0; i < n; i++)
237            {
238                store(formatter, section, option, i);
239            }
240        }
241    
242        void store(IniHandler formatter, Section section, String option, int index)
243        {
244            formatter.handleOption(option, section.get(option, index));
245        }
246    
247        private Section getOrAdd(String sectionName)
248        {
249            Section section = get(sectionName);
250    
251            return ((section == null)) ? add(sectionName) : section;
252        }
253    
254        private int parseOptionIndex(Matcher m)
255        {
256            return (m.group(G_OPTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_OPTION_IDX));
257        }
258    
259        private Section parseSection(Matcher m, Section owner)
260        {
261            String sectionName = m.group(G_SECTION);
262            int sectionIndex = parseSectionIndex(m);
263    
264            return (sectionName == null) ? owner : ((sectionIndex == -1) ? get(sectionName) : get(sectionName, sectionIndex));
265        }
266    
267        private int parseSectionIndex(Matcher m)
268        {
269            return (m.group(G_SECTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_SECTION_IDX));
270        }
271    
272        private final class BeanInvocationHandler extends AbstractBeanInvocationHandler
273        {
274            private final String _prefix;
275    
276            private BeanInvocationHandler(String prefix)
277            {
278                _prefix = prefix;
279            }
280    
281            @Override protected Object getPropertySpi(String property, Class<?> clazz)
282            {
283                String key = transform(property);
284                Object o = null;
285    
286                if (containsKey(key))
287                {
288                    if (clazz.isArray())
289                    {
290                        o = Array.newInstance(clazz.getComponentType(), length(key));
291                        for (int i = 0; i < length(key); i++)
292                        {
293                            Array.set(o, i, get(key, i).as(clazz.getComponentType()));
294                        }
295                    }
296                    else
297                    {
298                        o = get(key).as(clazz);
299                    }
300                }
301    
302                return o;
303            }
304    
305            @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
306            {
307                String key = transform(property);
308    
309                remove(key);
310                if (value != null)
311                {
312                    if (clazz.isArray())
313                    {
314                        for (int i = 0; i < Array.getLength(value); i++)
315                        {
316                            Section sec = add(key);
317    
318                            sec.from(Array.get(value, i));
319                        }
320                    }
321                    else
322                    {
323                        Section sec = add(key);
324    
325                        sec.from(value);
326                    }
327                }
328            }
329    
330            @Override protected boolean hasPropertySpi(String property)
331            {
332                return containsKey(transform(property));
333            }
334    
335            String transform(String property)
336            {
337                String ret = (_prefix == null) ? property : (_prefix + property);
338    
339                if (isPropertyFirstUpper())
340                {
341                    StringBuilder buff = new StringBuilder();
342    
343                    buff.append(Character.toUpperCase(property.charAt(0)));
344                    buff.append(property.substring(1));
345                    ret = buff.toString();
346                }
347    
348                return ret;
349            }
350        }
351    }