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