001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2011-2011 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.util; 011 012import org.eigenbase.util.property.*; 013 014import org.w3c.dom.*; 015 016import java.io.*; 017import java.lang.reflect.Field; 018import java.math.BigDecimal; 019import java.text.DecimalFormat; 020import java.util.*; 021import javax.xml.parsers.DocumentBuilder; 022import javax.xml.parsers.DocumentBuilderFactory; 023 024/** 025 * Utilities to generate MondrianProperties.java and mondrian.properties 026 * from property definitions in MondrianProperties.xml. 027 * 028 * @author jhyde 029 */ 030public class PropertyUtil { 031 /** 032 * Generates an XML file from a MondrianProperties instance. 033 * 034 * @param args Arguments 035 * @throws IllegalAccessException on error 036 */ 037 public static void main0(String[] args) throws IllegalAccessException { 038 Object properties1 = null; // MondrianProperties.instance(); 039 System.out.println("<PropertyDefinitions>"); 040 for (Field field : properties1.getClass().getFields()) { 041 org.eigenbase.util.property.Property o = 042 (org.eigenbase.util.property.Property) field.get(properties1); 043 System.out.println(" <PropertyDefinition>"); 044 System.out.println(" <Name>" + field.getName() + "</Name>"); 045 System.out.println(" <Path>" + o.getPath() + "</Path>"); 046 System.out.println( 047 " <Description>" + o.getPath() + "</Description>"); 048 System.out.println( 049 " <Type>" 050 + (o instanceof BooleanProperty ? "boolean" 051 : o instanceof IntegerProperty ? "int" 052 : o instanceof DoubleProperty ? "double" 053 : "String") 054 + "</Type>"); 055 if (o.getDefaultValue() != null) { 056 System.out.println( 057 " <Default>" 058 + o.getDefaultValue() + "</Default" + ">"); 059 } 060 System.out.println(" </PropertyDefinition>"); 061 } 062 System.out.println("</PropertyDefinitions>"); 063 } 064 065 private static Iterable<Node> iter(final NodeList nodeList) { 066 return new Iterable<Node>() { 067 public Iterator<Node> iterator() { 068 return new Iterator<Node>() { 069 int pos = 0; 070 071 public boolean hasNext() { 072 return pos < nodeList.getLength(); 073 } 074 075 public Node next() { 076 return nodeList.item(pos++); 077 } 078 079 public void remove() { 080 throw new UnsupportedOperationException(); 081 } 082 }; 083 } 084 }; 085 } 086 087 /** 088 * Generates MondrianProperties.java from MondrianProperties.xml. 089 * 090 * @param args Arguments 091 */ 092 public static void main(String[] args) 093 { 094 try { 095 new PropertyUtil().generate(); 096 } catch (Throwable e) { 097 System.out.println("Error while generating properties files."); 098 e.printStackTrace(); 099 } 100 } 101 102 private void generate() { 103 final File xmlFile = 104 new File("src/main/mondrian/olap", "MondrianProperties.xml"); 105 final File javaFile = 106 new File("src/main/mondrian/olap", "MondrianProperties.java"); 107 final File propertiesFile = 108 new File("mondrian.properties.template"); 109 final File htmlFile = new File("doc", "properties.html"); 110 111 SortedMap<String, PropertyDef> propertyDefinitionMap; 112 try { 113 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 114 dbf.setValidating(false); 115 dbf.setExpandEntityReferences(false); 116 DocumentBuilder db = dbf.newDocumentBuilder(); 117 Document doc = db.parse(xmlFile); 118 Element documentElement = doc.getDocumentElement(); 119 assert documentElement.getNodeName().equals("PropertyDefinitions"); 120 NodeList propertyDefinitions = 121 documentElement.getChildNodes(); 122 propertyDefinitionMap = new TreeMap<String, PropertyDef>(); 123 for (Node element : iter(propertyDefinitions)) { 124 if (element.getNodeName().equals("PropertyDefinition")) { 125 String name = getChildCdata(element, "Name"); 126 String dflt = getChildCdata(element, "Default"); 127 String type = getChildCdata(element, "Type"); 128 String path = getChildCdata(element, "Path"); 129 String category = getChildCdata(element, "Category"); 130 String core = getChildCdata(element, "Core"); 131 String description = getChildCdata(element, "Description"); 132 propertyDefinitionMap.put( 133 name, 134 new PropertyDef( 135 name, 136 path, 137 dflt, 138 category, 139 PropertyType.valueOf(type.toUpperCase()), 140 core == null || Boolean.valueOf(core), 141 description)); 142 } 143 } 144 } catch (Throwable e) { 145 throw new RuntimeException("Error while parsing " + xmlFile, e); 146 } 147 doGenerate(Generator.JAVA, propertyDefinitionMap, javaFile); 148 doGenerate(Generator.HTML, propertyDefinitionMap, htmlFile); 149 doGenerate(Generator.PROPERTIES, propertyDefinitionMap, propertiesFile); 150 } 151 152 void doGenerate( 153 Generator generator, 154 SortedMap<String, PropertyDef> propertyDefinitionMap, 155 File file) 156 { 157 FileWriter fw = null; 158 PrintWriter out = null; 159 boolean success = false; 160 try { 161 System.out.println("Generating " + file); 162 if (file.getParentFile() != null) { 163 file.getParentFile().mkdirs(); 164 } 165 fw = new FileWriter(file); 166 out = new PrintWriter(fw); 167 generator.generate(propertyDefinitionMap, file, out); 168 out.close(); 169 fw.close(); 170 success = true; 171 } catch (Throwable e) { 172 throw new RuntimeException("Error while generating " + file, e); 173 } finally { 174 if (out != null) { 175 out.close(); 176 } 177 if (fw != null) { 178 try { 179 fw.close(); 180 } catch (IOException e) { 181 // ignore 182 } 183 } 184 if (!success) { 185 file.delete(); 186 } 187 } 188 } 189 190 private static final void printLines(PrintWriter out, String[] lines) { 191 for (String line : lines) { 192 out.println(line); 193 } 194 } 195 196 enum Generator { 197 JAVA { 198 @Override 199 void generate( 200 SortedMap<String, PropertyDef> propertyDefinitionMap, 201 File file, 202 PrintWriter out) 203 { 204 out.println("// Generated from MondrianProperties.xml."); 205 out.println("package mondrian.olap;"); 206 out.println(); 207 out.println("import org.eigenbase.util.property.*;"); 208 out.println("import java.io.File;"); 209 out.println(); 210 211 printJavadoc( 212 out, "", 213 "Configuration properties that determine the\n" 214 + "behavior of a mondrian instance.\n" 215 + "\n" 216 + "<p>There is a method for property valid in a\n" 217 + "<code>mondrian.properties</code> file. Although it is possible to retrieve\n" 218 + "properties using the inherited {@link java.util.Properties#getProperty(String)}\n" 219 + "method, we recommend that you use methods in this class.</p>\n"); 220 String[] lines = { 221 "public class MondrianProperties extends MondrianPropertiesBase {", 222 " /**", 223 " * Properties, drawn from {@link System#getProperties},", 224 " * plus the contents of \"mondrian.properties\" if it", 225 " * exists. A singleton.", 226 " */", 227 " private static final MondrianProperties instance =", 228 " new MondrianProperties();", 229 "", 230 " private MondrianProperties() {", 231 " super(", 232 " new FilePropertySource(", 233 " new File(mondrianDotProperties)));", 234 " populate();", 235 " }", 236 "", 237 " /**", 238 " * Returns the singleton.", 239 " *", 240 " * @return Singleton instance", 241 " */", 242 " public static MondrianProperties instance() {", 243 " // NOTE: We used to instantiate on demand, but", 244 " // synchronization overhead was significant. See", 245 " // MONDRIAN-978.", 246 " return instance;", 247 " }", 248 "", 249 }; 250 printLines(out, lines); 251 for (PropertyDef def : propertyDefinitionMap.values()) { 252 if (!def.core) { 253 continue; 254 } 255 printJavadoc(out, " ", def.description); 256 out.println( 257 " public transient final " 258 + def.propertyType.className + " " + def.name + " ="); 259 out.println( 260 " new " + def.propertyType.className + "("); 261 out.println( 262 " this, \"" + def.path + "\", " 263 + "" + def.defaultJava() + ");"); 264 out.println(); 265 } 266 out.println("}"); 267 out.println(); 268 out.println("// End MondrianProperties.java"); 269 } 270 }, 271 272 HTML { 273 @Override 274 void generate( 275 SortedMap<String, PropertyDef> propertyDefinitionMap, 276 File file, 277 PrintWriter out) 278 { 279 out.println("<table>"); 280 out.println(" <tr>"); 281 out.println(" <td><strong>Property</strong></td>"); 282 out.println(" <td><strong>Type</strong></td>"); 283 out.println(" <td><strong>Default value</strong></td>"); 284 out.println(" <td><strong>Description</strong></td>"); 285 out.println(" </tr>"); 286 287 SortedSet<String> categories = new TreeSet<String>(); 288 for (PropertyDef def : propertyDefinitionMap.values()) { 289 categories.add(def.category); 290 } 291 for (String category : categories) { 292 out.println(" <tr>"); 293 out.println( 294 " <td colspan='4'><b><br>" + category + "</b" 295 + "></td>"); 296 out.println(" </tr>"); 297 for (PropertyDef def : propertyDefinitionMap.values()) { 298 if (!def.category.equals(category)) { 299 continue; 300 } 301 out.println(" <tr>"); 302 out.println( 303 "<td><code><a href='api/mondrian/olap/MondrianProperties.html#" 304 + def.name + "'>" + split(def.path) 305 + "</a></code></td>"); 306 out.println( 307 "<td>" + def.propertyType.name() .toLowerCase() 308 + "</td>"); 309 out.println( 310 "<td>" + split(def.defaultHtml()) + "</td>"); 311 out.println( 312 "<td>" + split(def.description) + "</td>"); 313 out.println(" </tr>"); 314 } 315 } 316 out.println("<table>"); 317 } 318 319 String split(String s) { 320 s = s.replaceAll("([,;=.])", "­$1­"); 321 if (!s.contains("<")) { 322 s = s.replaceAll("(/)", "­$1­"); 323 } 324 return s; 325 } 326 }, 327 328 PROPERTIES { 329 void generate( 330 SortedMap<String, PropertyDef> propertyDefinitionMap, 331 File file, 332 PrintWriter out) 333 { 334 printComments( 335 out, 336 "", 337 "#", 338 wrapText( 339 "This software is subject to the terms of the Eclipse Public License v1.0\n" 340 + "Agreement, available at the following URL:\n" 341 + "http://www.eclipse.org/legal/epl-v10.html.\n" 342 + "You must accept the terms of that agreement to use this software.\n" 343 + "\n" 344 + "Copyright (C) 2001-2005 Julian Hyde\n" 345 + "Copyright (C) 2005-2011 Pentaho and others\n" 346 + "All Rights Reserved.")); 347 out.println(); 348 349 char[] chars = new char[79]; 350 Arrays.fill(chars, '#'); 351 String commentLine = new String(chars); 352 for (PropertyDef def : propertyDefinitionMap.values()) { 353 out.println(commentLine); 354 printComments( 355 out, "", "#", wrapText(stripHtml(def.description))); 356 out.println("#"); 357 out.println( 358 "#" + def.path + "=" 359 + (def.defaultValue == null ? "" : def.defaultValue)); 360 out.println(); 361 } 362 printComments(out, "", "#", wrapText("End " + file.getName())); 363 } 364 }; 365 366 abstract void generate( 367 SortedMap<String, PropertyDef> propertyDefinitionMap, 368 File file, 369 PrintWriter out); 370 } 371 372 373 private static void printJavadoc( 374 PrintWriter out, String prefix, String content) 375 { 376 out.println(prefix + "/**"); 377 printComments(out, prefix, " *", wrapText(content)); 378 out.println(prefix + " */"); 379 } 380 381 private static void printComments( 382 PrintWriter out, 383 String offset, 384 String prefix, 385 List<String> strings) 386 { 387 for (String line : strings) { 388 if (line.length() > 0) { 389 out.println(offset + prefix + " " + line); 390 } else { 391 out.println(offset + prefix); 392 } 393 } 394 } 395 396 private static String quoteHtml(String s) { 397 return s.replaceAll("&", "&") 398 .replaceAll(">", ">") 399 .replaceAll("<", "<"); 400 } 401 402 private static String stripHtml(String s) { 403 s = s.replaceAll("<li>", "<li>* "); 404 s = s.replaceAll("<h3>", "<h3>### "); 405 s = s.replaceAll("</h3>", " ###</h3>"); 406 String[] strings = { 407 "p", "code", "br", "ul", "li", "blockquote", "h3", "i" }; 408 for (String string : strings) { 409 s = s.replaceAll("<" + string + "/>", ""); 410 s = s.replaceAll("<" + string + ">", ""); 411 s = s.replaceAll("</" + string + ">", ""); 412 } 413 s = replaceRegion(s, "{@code ", "}"); 414 s = replaceRegion(s, "{@link ", "}"); 415 s = s.replaceAll("&", "&"); 416 s = s.replaceAll("<", "<"); 417 s = s.replaceAll(">", ">"); 418 return s; 419 } 420 421 private static String replaceRegion(String s, String start, String end) { 422 int i = 0; 423 while ((i = s.indexOf(start, i)) >= 0) { 424 int j = s.indexOf(end, i); 425 if (j < 0) { 426 break; 427 } 428 s = s.substring(0, i) 429 + s.substring(i + start.length(), j) 430 + s.substring(j + 1); 431 i = j - start.length() - end.length(); 432 } 433 return s; 434 } 435 436 private static List<String> wrapText(String description) { 437 description = description.trim(); 438 return Arrays.asList(description.split("\n")); 439 } 440 441 private static String getChildCdata(Node element, String name) { 442 for (Node node : iter(element.getChildNodes())) { 443 if (node.getNodeName().equals(name)) { 444 StringBuilder buf = new StringBuilder(); 445 textRecurse(node, buf); 446 return buf.toString(); 447 } 448 } 449 return null; 450 } 451 452 private static void textRecurse(Node node, StringBuilder buf) { 453 for (Node node1 : iter(node.getChildNodes())) { 454 if (node1.getNodeType() == Node.CDATA_SECTION_NODE 455 || node1.getNodeType() == Node.TEXT_NODE) 456 { 457 buf.append(quoteHtml(node1.getTextContent())); 458 } 459 if (node1.getNodeType() == Node.ELEMENT_NODE) { 460 buf.append("<").append(node1.getNodeName()).append(">"); 461 textRecurse(node1, buf); 462 buf.append("</").append(node1.getNodeName()).append(">"); 463 } 464 } 465 } 466 467 private static class PropertyDef { 468 private final String name; 469 private final String defaultValue; 470 private final String category; 471 private final PropertyType propertyType; 472 private final boolean core; 473 private final String description; 474 private final String path; 475 476 PropertyDef( 477 String name, 478 String path, 479 String defaultValue, 480 String category, 481 PropertyType propertyType, 482 boolean core, 483 String description) 484 { 485 this.name = name; 486 this.path = path; 487 this.defaultValue = defaultValue; 488 this.category = category == null ? "Miscellaneous" : category; 489 this.propertyType = propertyType; 490 this.core = core; 491 this.description = description; 492 } 493 494 public String defaultJava() { 495 switch (propertyType) { 496 case STRING: 497 if (defaultValue == null) { 498 return "null"; 499 } else { 500 return "\"" + defaultValue.replaceAll("\"", "\\\"") + "\""; 501 } 502 default: 503 return defaultValue; 504 } 505 } 506 507 public String defaultHtml() { 508 if (defaultValue == null) { 509 return "-"; 510 } 511 switch (propertyType) { 512 case INT: 513 case DOUBLE: 514 return new DecimalFormat("#,###.#").format( 515 new BigDecimal(defaultValue)); 516 default: 517 return defaultValue; 518 } 519 } 520 } 521 522 private enum PropertyType { 523 INT("IntegerProperty"), 524 STRING("StringProperty"), 525 DOUBLE("DoubleProperty"), 526 BOOLEAN("BooleanProperty"); 527 528 public final String className; 529 530 PropertyType(String className) { 531 this.className = className; 532 } 533 } 534} 535 536// End PropertyUtil.java