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 }