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