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 boolean remove(Object sectionName, Object optionName)
142    {
143        Section sec = get(sectionName);
144        if (sec == null) {
145            return false;
146        }
147        if (sec.containsKey(optionName)) {
148            sec.remove(optionName);
149            return true;
150        }
151        return false;
152    }
153
154    boolean isTreeMode()
155    {
156        return _treeMode;
157    }
158
159    char getPathSeparator()
160    {
161        return PATH_SEPARATOR;
162    }
163
164    boolean isPropertyFirstUpper()
165    {
166        return _propertyFirstUpper;
167    }
168
169    Section newSection(String name)
170    {
171        return new BasicProfileSection(this, name);
172    }
173
174    void resolve(StringBuilder buffer, Section owner)
175    {
176        Matcher m = EXPRESSION.matcher(buffer);
177
178        while (m.find())
179        {
180            String sectionName = m.group(G_SECTION);
181            String optionName = m.group(G_OPTION);
182            int optionIndex = parseOptionIndex(m);
183            Section section = parseSection(m, owner);
184            String value = null;
185
186            if (SECTION_ENVIRONMENT.equals(sectionName))
187            {
188                value = Config.getEnvironment(optionName);
189            }
190            else if (SECTION_SYSTEM_PROPERTIES.equals(sectionName))
191            {
192                value = Config.getSystemProperty(optionName);
193            }
194            else if (section != null)
195            {
196                value = (optionIndex == -1) ? section.fetch(optionName) : section.fetch(optionName, optionIndex);
197            }
198
199            if (value != null)
200            {
201                buffer.replace(m.start(), m.end(), value);
202                m.reset(buffer);
203            }
204        }
205    }
206
207    void store(IniHandler formatter)
208    {
209        formatter.startIni();
210        store(formatter, getComment());
211        for (Ini.Section s : values())
212        {
213            store(formatter, s);
214        }
215
216        formatter.endIni();
217    }
218
219    void store(IniHandler formatter, Section s)
220    {
221        store(formatter, getComment(s.getName()));
222        formatter.startSection(s.getName());
223        for (String name : s.keySet())
224        {
225            store(formatter, s, name);
226        }
227
228        formatter.endSection();
229    }
230
231    void store(IniHandler formatter, String comment)
232    {
233        if ((comment != null) && (comment.length() != 0))
234        {
235            formatter.handleComment(comment);
236        }
237    }
238
239    void store(IniHandler formatter, Section section, String option)
240    {
241        store(formatter, section.getComment(option));
242        int n = section.length(option);
243
244        for (int i = 0; i < n; i++)
245        {
246            store(formatter, section, option, i);
247        }
248    }
249
250    void store(IniHandler formatter, Section section, String option, int index)
251    {
252        formatter.handleOption(option, section.get(option, index));
253    }
254
255    private Section getOrAdd(String sectionName)
256    {
257        Section section = get(sectionName);
258
259        return ((section == null)) ? add(sectionName) : section;
260    }
261
262    private int parseOptionIndex(Matcher m)
263    {
264        return (m.group(G_OPTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_OPTION_IDX));
265    }
266
267    private Section parseSection(Matcher m, Section owner)
268    {
269        String sectionName = m.group(G_SECTION);
270        int sectionIndex = parseSectionIndex(m);
271
272        return (sectionName == null) ? owner : ((sectionIndex == -1) ? get(sectionName) : get(sectionName, sectionIndex));
273    }
274
275    private int parseSectionIndex(Matcher m)
276    {
277        return (m.group(G_SECTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_SECTION_IDX));
278    }
279
280    private final class BeanInvocationHandler extends AbstractBeanInvocationHandler
281    {
282        private final String _prefix;
283
284        private BeanInvocationHandler(String prefix)
285        {
286            _prefix = prefix;
287        }
288
289        @Override protected Object getPropertySpi(String property, Class<?> clazz)
290        {
291            String key = transform(property);
292            Object o = null;
293
294            if (containsKey(key))
295            {
296                if (clazz.isArray())
297                {
298                    o = Array.newInstance(clazz.getComponentType(), length(key));
299                    for (int i = 0; i < length(key); i++)
300                    {
301                        Array.set(o, i, get(key, i).as(clazz.getComponentType()));
302                    }
303                }
304                else
305                {
306                    o = get(key).as(clazz);
307                }
308            }
309
310            return o;
311        }
312
313        @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
314        {
315            String key = transform(property);
316
317            remove(key);
318            if (value != null)
319            {
320                if (clazz.isArray())
321                {
322                    for (int i = 0; i < Array.getLength(value); i++)
323                    {
324                        Section sec = add(key);
325
326                        sec.from(Array.get(value, i));
327                    }
328                }
329                else
330                {
331                    Section sec = add(key);
332
333                    sec.from(value);
334                }
335            }
336        }
337
338        @Override protected boolean hasPropertySpi(String property)
339        {
340            return containsKey(transform(property));
341        }
342
343        String transform(String property)
344        {
345            String ret = (_prefix == null) ? property : (_prefix + property);
346
347            if (isPropertyFirstUpper())
348            {
349                StringBuilder buff = new StringBuilder();
350
351                buff.append(Character.toUpperCase(property.charAt(0)));
352                buff.append(property.substring(1));
353                ret = buff.toString();
354            }
355
356            return ret;
357        }
358    }
359}