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.BeanAccess;
019import org.ini4j.spi.BeanTool;
020import org.ini4j.spi.Warnings;
021
022import java.lang.reflect.Array;
023
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026
027public class BasicOptionMap extends CommonMultiMap<String, String> implements OptionMap
028{
029    private static final char SUBST_CHAR = '$';
030    private static final String SYSTEM_PROPERTY_PREFIX = "@prop/";
031    private static final String ENVIRONMENT_PREFIX = "@env/";
032    private static final int SYSTEM_PROPERTY_PREFIX_LEN = SYSTEM_PROPERTY_PREFIX.length();
033    private static final int ENVIRONMENT_PREFIX_LEN = ENVIRONMENT_PREFIX.length();
034    private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\$\\{(([^\\[]+)(\\[([0-9]+)\\])?)\\}");
035    private static final int G_OPTION = 2;
036    private static final int G_INDEX = 4;
037    private static final long serialVersionUID = 325469712293707584L;
038    private BeanAccess _defaultBeanAccess;
039    private final boolean _propertyFirstUpper;
040
041    public BasicOptionMap()
042    {
043        this(false);
044    }
045
046    public BasicOptionMap(boolean propertyFirstUpper)
047    {
048        _propertyFirstUpper = propertyFirstUpper;
049    }
050
051    @Override @SuppressWarnings(Warnings.UNCHECKED)
052    public <T> T getAll(Object key, Class<T> clazz)
053    {
054        requireArray(clazz);
055        T value;
056
057        value = (T) Array.newInstance(clazz.getComponentType(), length(key));
058        for (int i = 0; i < length(key); i++)
059        {
060            Array.set(value, i, BeanTool.getInstance().parse(get(key, i), clazz.getComponentType()));
061        }
062
063        return value;
064    }
065
066    @Override public void add(String key, Object value)
067    {
068        super.add(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value));
069    }
070
071    @Override public void add(String key, Object value, int index)
072    {
073        super.add(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value), index);
074    }
075
076    @Override public <T> T as(Class<T> clazz)
077    {
078        return BeanTool.getInstance().proxy(clazz, getDefaultBeanAccess());
079    }
080
081    @Override public <T> T as(Class<T> clazz, String keyPrefix)
082    {
083        return BeanTool.getInstance().proxy(clazz, newBeanAccess(keyPrefix));
084    }
085
086    @Override public String fetch(Object key)
087    {
088        int len = length(key);
089
090        return (len == 0) ? null : fetch(key, len - 1);
091    }
092
093    @Override public String fetch(Object key, int index)
094    {
095        String value = get(key, index);
096
097        if ((value != null) && (value.indexOf(SUBST_CHAR) >= 0))
098        {
099            StringBuilder buffer = new StringBuilder(value);
100
101            resolve(buffer);
102            value = buffer.toString();
103        }
104
105        return value;
106    }
107
108    @Override public <T> T fetch(Object key, Class<T> clazz)
109    {
110        return BeanTool.getInstance().parse(fetch(key), clazz);
111    }
112
113    @Override public <T> T fetch(Object key, int index, Class<T> clazz)
114    {
115        return BeanTool.getInstance().parse(fetch(key, index), clazz);
116    }
117
118    @Override @SuppressWarnings(Warnings.UNCHECKED)
119    public <T> T fetchAll(Object key, Class<T> clazz)
120    {
121        requireArray(clazz);
122        T value;
123
124        value = (T) Array.newInstance(clazz.getComponentType(), length(key));
125        for (int i = 0; i < length(key); i++)
126        {
127            Array.set(value, i, BeanTool.getInstance().parse(fetch(key, i), clazz.getComponentType()));
128        }
129
130        return value;
131    }
132
133    @Override public void from(Object bean)
134    {
135        BeanTool.getInstance().inject(getDefaultBeanAccess(), bean);
136    }
137
138    @Override public void from(Object bean, String keyPrefix)
139    {
140        BeanTool.getInstance().inject(newBeanAccess(keyPrefix), bean);
141    }
142
143    @Override public <T> T get(Object key, Class<T> clazz)
144    {
145        return BeanTool.getInstance().parse(get(key), clazz);
146    }
147
148    @Override public <T> T get(Object key, int index, Class<T> clazz)
149    {
150        return BeanTool.getInstance().parse(get(key, index), clazz);
151    }
152
153    @Override public String put(String key, Object value)
154    {
155        return super.put(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value));
156    }
157
158    @Override public String put(String key, Object value, int index)
159    {
160        return super.put(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value), index);
161    }
162
163    @Override public void putAll(String key, Object value)
164    {
165        if (value != null)
166        {
167            requireArray(value.getClass());
168        }
169
170        remove(key);
171        if (value != null)
172        {
173            int n = Array.getLength(value);
174
175            for (int i = 0; i < n; i++)
176            {
177                add(key, Array.get(value, i));
178            }
179        }
180    }
181
182    @Override public void to(Object bean)
183    {
184        BeanTool.getInstance().inject(bean, getDefaultBeanAccess());
185    }
186
187    @Override public void to(Object bean, String keyPrefix)
188    {
189        BeanTool.getInstance().inject(bean, newBeanAccess(keyPrefix));
190    }
191
192    synchronized BeanAccess getDefaultBeanAccess()
193    {
194        if (_defaultBeanAccess == null)
195        {
196            _defaultBeanAccess = newBeanAccess();
197        }
198
199        return _defaultBeanAccess;
200    }
201
202    boolean isPropertyFirstUpper()
203    {
204        return _propertyFirstUpper;
205    }
206
207    BeanAccess newBeanAccess()
208    {
209        return new Access();
210    }
211
212    BeanAccess newBeanAccess(String propertyNamePrefix)
213    {
214        return new Access(propertyNamePrefix);
215    }
216
217    void resolve(StringBuilder buffer)
218    {
219        Matcher m = EXPRESSION.matcher(buffer);
220
221        while (m.find())
222        {
223            String name = m.group(G_OPTION);
224            int index = (m.group(G_INDEX) == null) ? -1 : Integer.parseInt(m.group(G_INDEX));
225            String value;
226
227            if (name.startsWith(ENVIRONMENT_PREFIX))
228            {
229                value = Config.getEnvironment(name.substring(ENVIRONMENT_PREFIX_LEN));
230            }
231            else if (name.startsWith(SYSTEM_PROPERTY_PREFIX))
232            {
233                value = Config.getSystemProperty(name.substring(SYSTEM_PROPERTY_PREFIX_LEN));
234            }
235            else
236            {
237                value = (index == -1) ? fetch(name) : fetch(name, index);
238            }
239
240            if (value != null)
241            {
242                buffer.replace(m.start(), m.end(), value);
243                m.reset(buffer);
244            }
245        }
246    }
247
248    private void requireArray(Class clazz)
249    {
250        if (!clazz.isArray())
251        {
252            throw new IllegalArgumentException("Array required");
253        }
254    }
255
256    class Access implements BeanAccess
257    {
258        private final String _prefix;
259
260        Access()
261        {
262            this(null);
263        }
264
265        Access(String prefix)
266        {
267            _prefix = prefix;
268        }
269
270        @Override public void propAdd(String propertyName, String value)
271        {
272            add(transform(propertyName), value);
273        }
274
275        @Override public String propDel(String propertyName)
276        {
277            return remove(transform(propertyName));
278        }
279
280        @Override public String propGet(String propertyName)
281        {
282            return fetch(transform(propertyName));
283        }
284
285        @Override public String propGet(String propertyName, int index)
286        {
287            return fetch(transform(propertyName), index);
288        }
289
290        @Override public int propLength(String propertyName)
291        {
292            return length(transform(propertyName));
293        }
294
295        @Override public String propSet(String propertyName, String value)
296        {
297            return put(transform(propertyName), value);
298        }
299
300        @Override public String propSet(String propertyName, String value, int index)
301        {
302            return put(transform(propertyName), value, index);
303        }
304
305        private String transform(String orig)
306        {
307            String ret = orig;
308
309            if (((_prefix != null) || isPropertyFirstUpper()) && (orig != null))
310            {
311                StringBuilder buff = new StringBuilder();
312
313                if (_prefix != null)
314                {
315                    buff.append(_prefix);
316                }
317
318                if (isPropertyFirstUpper())
319                {
320                    buff.append(Character.toUpperCase(orig.charAt(0)));
321                    buff.append(orig.substring(1));
322                }
323                else
324                {
325                    buff.append(orig);
326                }
327
328                ret = buff.toString();
329            }
330
331            return ret;
332        }
333    }
334}