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("(?<!\\\\)\\$\\{(([^\\[]+)(\\[([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    }