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.IniFormatter;
020 import org.ini4j.spi.XMLFormatter;
021
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.io.InputStreamReader;
025 import java.io.OutputStream;
026 import java.io.Reader;
027 import java.io.Writer;
028
029 import java.lang.reflect.Array;
030 import java.lang.reflect.Proxy;
031
032 import java.net.URL;
033
034 import java.util.HashMap;
035 import java.util.Map;
036 import java.util.regex.Matcher;
037 import java.util.regex.Pattern;
038
039 public class Ini extends MultiMapImpl<String, Ini.Section>
040 {
041 private static final String SECTION_SYSTEM_PROPERTIES = "@prop";
042 private static final String SECTION_ENVIRONMENT = "@env";
043 private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\$\\{(([^\\[]+)(\\[([0-9]+)\\])?/)?([^\\[]+)(\\[(([0-9]+))\\])?\\}");
044 private static final int G_SECTION = 2;
045 private static final int G_SECTION_IDX = 4;
046 private static final int G_OPTION = 5;
047 private static final int G_OPTION_IDX = 7;
048 private Map<Class, Object> _beans;
049 private Config _config = Config.getGlobal();
050
051 public Ini()
052 {
053 assert true;
054 }
055
056 public Ini(Reader input) throws IOException, InvalidIniFormatException
057 {
058 this();
059 load(input);
060 }
061
062 public Ini(InputStream input) throws IOException, InvalidIniFormatException
063 {
064 this();
065 load(input);
066 }
067
068 public Ini(URL input) throws IOException, InvalidIniFormatException
069 {
070 this();
071 load(input);
072 }
073
074 public void setConfig(Config value)
075 {
076 _config = value;
077 }
078
079 public Section add(String name)
080 {
081 Section s = new Section(name);
082
083 if (getConfig().isMultiSection())
084 {
085 add(name, s);
086 }
087 else
088 {
089 put(name, s);
090 }
091
092 return s;
093 }
094
095 public <T> T as(Class<T> clazz)
096 {
097 return clazz.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { clazz }, new BeanInvocationHandler()));
098 }
099
100 public void load(InputStream input) throws IOException, InvalidIniFormatException
101 {
102 IniParser.newInstance(getConfig()).parse(input, new Builder());
103 }
104
105 public void load(Reader input) throws IOException, InvalidIniFormatException
106 {
107 IniParser.newInstance(getConfig()).parse(input, new Builder());
108 }
109
110 public void load(URL input) throws IOException, InvalidIniFormatException
111 {
112 IniParser.newInstance(getConfig()).parse(input, new Builder());
113 }
114
115 public void loadFromXML(InputStream input) throws IOException, InvalidIniFormatException
116 {
117 loadFromXML(new InputStreamReader(input));
118 }
119
120 public void loadFromXML(Reader input) throws IOException, InvalidIniFormatException
121 {
122 Builder builder = new Builder();
123
124 IniParser.newInstance(getConfig()).parseXML(input, builder);
125 }
126
127 public void loadFromXML(URL input) throws IOException, InvalidIniFormatException
128 {
129 Builder builder = new Builder();
130
131 IniParser.newInstance(getConfig()).parseXML(input, builder);
132 }
133
134 public Section remove(Section section)
135 {
136 return remove((Object) section.getName());
137 }
138
139 public void store(OutputStream output) throws IOException
140 {
141 store(IniFormatter.newInstance(output, getConfig()));
142 }
143
144 public void store(Writer output) throws IOException
145 {
146 store(IniFormatter.newInstance(output, getConfig()));
147 }
148
149 public void storeToXML(OutputStream output) throws IOException
150 {
151 store(XMLFormatter.newInstance(output));
152 }
153
154 public void storeToXML(Writer output) throws IOException
155 {
156 store(XMLFormatter.newInstance(output));
157 }
158
159 @Deprecated public synchronized <T> T to(Class<T> clazz)
160 {
161 Object bean = null;
162
163 if (_beans == null)
164 {
165 _beans = new HashMap<Class, Object>();
166 }
167 else
168 {
169 bean = _beans.get(clazz);
170 }
171
172 if (bean == null)
173 {
174 bean = as(clazz);
175 _beans.put(clazz, bean);
176 }
177
178 return clazz.cast(bean);
179 }
180
181 protected Config getConfig()
182 {
183 return _config;
184 }
185
186 protected void resolve(StringBuilder buffer, Section owner)
187 {
188 Matcher m = EXPRESSION.matcher(buffer);
189
190 while (m.find())
191 {
192 String sectionName = m.group(G_SECTION);
193 String optionName = m.group(G_OPTION);
194 int optionIndex = parseOptionIndex(m);
195 Section section = parseSection(m, owner);
196 String value = null;
197
198 if (SECTION_ENVIRONMENT.equals(sectionName))
199 {
200 value = System.getenv(optionName);
201 }
202 else if (SECTION_SYSTEM_PROPERTIES.equals(sectionName))
203 {
204 value = System.getProperty(optionName);
205 }
206 else if (section != null)
207 {
208 value = (optionIndex == -1) ? section.fetch(optionName) : section.fetch(optionName, optionIndex);
209 }
210
211 if (value != null)
212 {
213 buffer.replace(m.start(), m.end(), value);
214 m.reset(buffer);
215 }
216 }
217 }
218
219 protected void store(IniHandler formatter) throws IOException
220 {
221 formatter.startIni();
222 for (Ini.Section s : values())
223 {
224 formatter.startSection(s.getName());
225 for (String name : s.keySet())
226 {
227 int n = getConfig().isMultiOption() ? s.length(name) : 1;
228
229 for (int i = 0; i < n; i++)
230 {
231 formatter.handleOption(name, s.get(name, i));
232 }
233 }
234
235 formatter.endSection();
236 }
237
238 formatter.endIni();
239 }
240
241 private int parseOptionIndex(Matcher m)
242 {
243 return (m.group(G_OPTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_OPTION_IDX));
244 }
245
246 private Section parseSection(Matcher m, Section owner)
247 {
248 String sectionName = m.group(G_SECTION);
249 int sectionIndex = parseSectionIndex(m);
250
251 return (sectionName == null) ? owner : ((sectionIndex == -1) ? get(sectionName) : get(sectionName, sectionIndex));
252 }
253
254 private int parseSectionIndex(Matcher m)
255 {
256 return (m.group(G_SECTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_SECTION_IDX));
257 }
258
259 public class Section extends OptionMapImpl
260 {
261 private Map<Class, Object> _beans;
262 private final String _name;
263
264 public Section(String name)
265 {
266 super();
267 _name = name;
268 }
269
270 public String getName()
271 {
272 return _name;
273 }
274
275 @Deprecated public synchronized <T> T to(Class<T> clazz)
276 {
277 Object bean = null;
278
279 if (_beans == null)
280 {
281 _beans = new HashMap<Class, Object>();
282 }
283 else
284 {
285 bean = _beans.get(clazz);
286 }
287
288 if (bean == null)
289 {
290 bean = as(clazz);
291 _beans.put(clazz, bean);
292 }
293
294 return clazz.cast(bean);
295 }
296
297 @Override protected void resolve(StringBuilder buffer)
298 {
299 Ini.this.resolve(buffer, this);
300 }
301 }
302
303 private class BeanInvocationHandler extends AbstractBeanInvocationHandler
304 {
305 private final MultiMap<String, Object> _sectionBeans = new MultiMapImpl<String, Object>();
306
307 @Override protected Object getPropertySpi(String property, Class<?> clazz)
308 {
309 Object o = null;
310
311 if (clazz.isArray())
312 {
313 if (!_sectionBeans.containsKey(property) && containsKey(property))
314 {
315 for (int i = 0; i < length(property); i++)
316 {
317 _sectionBeans.add(property, get(property, i).as(clazz.getComponentType()));
318 }
319 }
320
321 if (_sectionBeans.containsKey(property))
322 {
323 o = Array.newInstance(clazz.getComponentType(), _sectionBeans.length(property));
324 for (int i = 0; i < _sectionBeans.length(property); i++)
325 {
326 Array.set(o, i, _sectionBeans.get(property, i));
327 }
328 }
329 }
330 else
331 {
332 o = _sectionBeans.get(property);
333 if (o == null)
334 {
335 Section section = get(property);
336
337 if (section != null)
338 {
339 o = section.as(clazz);
340 _sectionBeans.put(property, o);
341 }
342 }
343 }
344
345 return o;
346 }
347
348 @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
349 {
350 remove(property);
351 if (value != null)
352 {
353 if (clazz.isArray())
354 {
355 for (int i = 0; i < Array.getLength(value); i++)
356 {
357 Section sec = add(property);
358
359 sec.from(Array.get(value, i));
360 }
361 }
362 else
363 {
364 Section sec = add(property);
365
366 sec.from(value);
367 }
368 }
369 }
370
371 @Override protected boolean hasPropertySpi(String property)
372 {
373 return containsKey(property);
374 }
375 }
376
377 private class Builder implements IniHandler
378 {
379 private Section _currentSection;
380
381 public void endIni()
382 {
383 assert true;
384 }
385
386 @Override public void endSection()
387 {
388 _currentSection = null;
389 }
390
391 @Override public void handleOption(String name, String value)
392 {
393 if (getConfig().isMultiOption())
394 {
395 _currentSection.add(name, value);
396 }
397 else
398 {
399 _currentSection.put(name, value);
400 }
401 }
402
403 public void startIni()
404 {
405 assert true;
406 }
407
408 @Override public void startSection(String sectionName)
409 {
410 if (getConfig().isMultiSection())
411 {
412 _currentSection = add(sectionName);
413 }
414 else
415 {
416 Section s = get(sectionName);
417
418 _currentSection = (s == null) ? add(sectionName) : s;
419 }
420 }
421 }
422 }