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 */
016package org.ini4j;
017
018import org.ini4j.spi.AbstractBeanInvocationHandler;
019import org.ini4j.spi.BeanTool;
020import org.ini4j.spi.IniHandler;
021
022import java.lang.reflect.Array;
023import java.lang.reflect.Proxy;
024
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028public 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}