001 /** 002 * This software is provided under the terms of the Minecraft Forge Public 003 * License v1.0. 004 */ 005 006 package net.minecraftforge.common; 007 008 import java.io.BufferedReader; 009 import java.io.BufferedWriter; 010 import java.io.File; 011 import java.io.FileInputStream; 012 import java.io.FileOutputStream; 013 import java.io.IOException; 014 import java.io.InputStreamReader; 015 import java.io.OutputStreamWriter; 016 import java.text.DateFormat; 017 import java.util.Collection; 018 import java.util.Date; 019 import java.util.Locale; 020 import java.util.Map; 021 import java.util.TreeMap; 022 023 import com.google.common.base.CharMatcher; 024 import com.google.common.base.Splitter; 025 import com.google.common.collect.Maps; 026 027 import net.minecraft.src.Block; 028 029 /** 030 * This class offers advanced configurations capabilities, allowing to provide 031 * various categories for configuration variables. 032 */ 033 public class Configuration 034 { 035 036 private boolean configBlocks[] = null; 037 038 public static final String CATEGORY_GENERAL = "general"; 039 public static final String CATEGORY_BLOCK = "block"; 040 public static final String CATEGORY_ITEM = "item"; 041 042 File file; 043 044 public Map<String, Map<String, Property>> categories = new TreeMap<String, Map<String, Property>>(); 045 046 public TreeMap<String, Property> blockProperties = new TreeMap<String, Property>(); 047 public TreeMap<String, Property> itemProperties = new TreeMap<String, Property>(); 048 public TreeMap<String, Property> generalProperties = new TreeMap<String, Property>(); 049 050 private Map<String,String> customCategoryComments = Maps.newHashMap(); 051 private boolean caseSensitiveCustomCategories; 052 public static final String ALLOWED_CHARS = "._-"; 053 054 private static final CharMatcher allowedProperties = CharMatcher.JAVA_LETTER_OR_DIGIT.or(CharMatcher.anyOf(ALLOWED_CHARS)); 055 056 /** 057 * Create a configuration file for the file given in parameter. 058 */ 059 public Configuration(File file) 060 { 061 this.file = file; 062 categories.put(CATEGORY_GENERAL, generalProperties); 063 categories.put(CATEGORY_BLOCK, blockProperties); 064 categories.put(CATEGORY_ITEM, itemProperties); 065 } 066 067 public Configuration(File file, boolean caseSensitiveCustomCategories) 068 { 069 this(file); 070 this.caseSensitiveCustomCategories = caseSensitiveCustomCategories; 071 } 072 /** 073 * Gets or create a block id property. If the block id property key is 074 * already in the configuration, then it will be used. Otherwise, 075 * defaultId will be used, except if already taken, in which case this 076 * will try to determine a free default id. 077 */ 078 public Property getOrCreateBlockIdProperty(String key, int defaultId) 079 { 080 if (configBlocks == null) 081 { 082 configBlocks = new boolean[Block.blocksList.length]; 083 084 for (int i = 0; i < configBlocks.length; ++i) 085 { 086 configBlocks[i] = false; 087 } 088 } 089 090 Map<String, Property> properties = categories.get(CATEGORY_BLOCK); 091 if (properties.containsKey(key)) 092 { 093 Property property = getOrCreateIntProperty(key, Configuration.CATEGORY_BLOCK, defaultId); 094 configBlocks[Integer.parseInt(property.value)] = true; 095 return property; 096 } 097 else 098 { 099 Property property = new Property(); 100 properties.put(key, property); 101 property.setName(key); 102 103 if (Block.blocksList[defaultId] == null && !configBlocks[defaultId]) 104 { 105 property.value = Integer.toString(defaultId); 106 configBlocks[defaultId] = true; 107 return property; 108 } 109 else 110 { 111 for (int j = configBlocks.length - 1; j >= 0; --j) 112 { 113 if (Block.blocksList[j] == null && !configBlocks[j]) 114 { 115 property.value = Integer.toString(j); 116 configBlocks[j] = true; 117 return property; 118 } 119 } 120 121 throw new RuntimeException("No more block ids available for " + key); 122 } 123 } 124 } 125 126 public Property getOrCreateIntProperty(String key, String category, int defaultValue) 127 { 128 Property prop = getOrCreateProperty(key, category, Integer.toString(defaultValue)); 129 try 130 { 131 Integer.parseInt(prop.value); 132 return prop; 133 } 134 catch (NumberFormatException e) 135 { 136 prop.value = Integer.toString(defaultValue); 137 return prop; 138 } 139 } 140 141 public Property getOrCreateBooleanProperty(String key, String category, boolean defaultValue) 142 { 143 Property prop = getOrCreateProperty(key, category, Boolean.toString(defaultValue)); 144 if ("true".equals(prop.value.toLowerCase(Locale.ENGLISH)) || "false".equals(prop.value.toLowerCase(Locale.ENGLISH))) 145 { 146 return prop; 147 } 148 else 149 { 150 prop.value = Boolean.toString(defaultValue); 151 return prop; 152 } 153 } 154 155 public Property getOrCreateProperty(String key, String category, String defaultValue) 156 { 157 if (!caseSensitiveCustomCategories) 158 { 159 category = category.toLowerCase(Locale.ENGLISH); 160 } 161 Map<String, Property> source = categories.get(category); 162 163 if(source == null) 164 { 165 source = new TreeMap<String, Property>(); 166 categories.put(category, source); 167 } 168 169 if (source.containsKey(key)) 170 { 171 return source.get(key); 172 } 173 else if (defaultValue != null) 174 { 175 Property property = new Property(); 176 177 source.put(key, property); 178 property.setName(key); 179 180 property.value = defaultValue; 181 return property; 182 } 183 else 184 { 185 return null; 186 } 187 } 188 189 public void load() 190 { 191 BufferedReader buffer = null; 192 try 193 { 194 if (file.getParentFile() != null) 195 { 196 file.getParentFile().mkdirs(); 197 } 198 199 if (!file.exists() && !file.createNewFile()) 200 { 201 return; 202 } 203 204 if (file.canRead()) 205 { 206 FileInputStream fileinputstream = new FileInputStream(file); 207 buffer = new BufferedReader(new InputStreamReader(fileinputstream, "UTF-8")); 208 209 String line; 210 Map<String, Property> currentMap = null; 211 212 while (true) 213 { 214 line = buffer.readLine(); 215 216 if (line == null) 217 { 218 break; 219 } 220 221 int nameStart = -1, nameEnd = -1; 222 boolean skip = false; 223 boolean quoted = false; 224 for (int i = 0; i < line.length() && !skip; ++i) 225 { 226 if (Character.isLetterOrDigit(line.charAt(i)) || ALLOWED_CHARS.indexOf(line.charAt(i)) != -1 || (quoted && line.charAt(i) != '"')) 227 { 228 if (nameStart == -1) 229 { 230 nameStart = i; 231 } 232 233 nameEnd = i; 234 } 235 else if (Character.isWhitespace(line.charAt(i))) 236 { 237 // ignore space charaters 238 } 239 else 240 { 241 switch (line.charAt(i)) 242 { 243 case '#': 244 skip = true; 245 continue; 246 247 case '"': 248 if (quoted) 249 { 250 quoted = false; 251 } 252 if (!quoted && nameStart == -1) 253 { 254 quoted = true; 255 } 256 break; 257 case '{': 258 String scopeName = line.substring(nameStart, nameEnd + 1); 259 260 currentMap = categories.get(scopeName); 261 if (currentMap == null) 262 { 263 currentMap = new TreeMap<String, Property>(); 264 categories.put(scopeName, currentMap); 265 } 266 267 break; 268 269 case '}': 270 currentMap = null; 271 break; 272 273 case '=': 274 String propertyName = line.substring(nameStart, nameEnd + 1); 275 276 if (currentMap == null) 277 { 278 throw new RuntimeException("property " + propertyName + " has no scope"); 279 } 280 281 Property prop = new Property(); 282 prop.setName(propertyName); 283 prop.value = line.substring(i + 1); 284 i = line.length(); 285 286 currentMap.put(propertyName, prop); 287 288 break; 289 290 default: 291 throw new RuntimeException("unknown character " + line.charAt(i)); 292 } 293 } 294 } 295 if (quoted) 296 { 297 throw new RuntimeException("unmatched quote"); 298 } 299 } 300 } 301 } 302 catch (IOException e) 303 { 304 e.printStackTrace(); 305 } 306 finally 307 { 308 if (buffer != null) 309 { 310 try 311 { 312 buffer.close(); 313 } catch (IOException e){} 314 } 315 } 316 } 317 318 public void save() 319 { 320 try 321 { 322 if (file.getParentFile() != null) 323 { 324 file.getParentFile().mkdirs(); 325 } 326 327 if (!file.exists() && !file.createNewFile()) 328 { 329 return; 330 } 331 332 if (file.canWrite()) 333 { 334 FileOutputStream fos = new FileOutputStream(file); 335 BufferedWriter buffer = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8")); 336 337 buffer.write("# Configuration file\r\n"); 338 buffer.write("# Generated on " + DateFormat.getInstance().format(new Date()) + "\r\n"); 339 buffer.write("\r\n"); 340 341 for(Map.Entry<String, Map<String, Property>> category : categories.entrySet()) 342 { 343 buffer.write("####################\r\n"); 344 buffer.write("# " + category.getKey() + " \r\n"); 345 if (customCategoryComments.containsKey(category.getKey())) 346 { 347 buffer.write("#===================\r\n"); 348 String comment = customCategoryComments.get(category.getKey()); 349 Splitter splitter = Splitter.onPattern("\r?\n"); 350 for (String commentLine : splitter.split(comment)) 351 { 352 buffer.write("# "); 353 buffer.write(commentLine+"\r\n"); 354 } 355 } 356 buffer.write("####################\r\n\r\n"); 357 358 String catKey = category.getKey(); 359 if (!allowedProperties.matchesAllOf(catKey)) 360 { 361 catKey = '"'+catKey+'"'; 362 } 363 buffer.write(catKey + " {\r\n"); 364 writeProperties(buffer, category.getValue().values()); 365 buffer.write("}\r\n\r\n"); 366 } 367 368 buffer.close(); 369 fos.close(); 370 } 371 } 372 catch (IOException e) 373 { 374 e.printStackTrace(); 375 } 376 } 377 378 public void addCustomCategoryComment(String category, String comment) 379 { 380 if (!caseSensitiveCustomCategories) 381 category = category.toLowerCase(Locale.ENGLISH); 382 customCategoryComments.put(category, comment); 383 } 384 385 private void writeProperties(BufferedWriter buffer, Collection<Property> props) throws IOException 386 { 387 for (Property property : props) 388 { 389 if (property.comment != null) 390 { 391 Splitter splitter = Splitter.onPattern("\r?\n"); 392 for (String commentLine : splitter.split(property.comment)) 393 { 394 buffer.write(" # " + commentLine + "\r\n"); 395 } 396 } 397 String propName = property.getName(); 398 if (!allowedProperties.matchesAllOf(propName)) 399 { 400 propName = '"'+propName+'"'; 401 } 402 buffer.write(" " + propName + "=" + property.value); 403 buffer.write("\r\n"); 404 } 405 } 406 }