/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package mondrian.util;
import org.eigenbase.util.property.*;
import org.w3c.dom.*;
import java.io.*;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
/**
* Utilities to generate MondrianProperties.java and mondrian.properties
* from property definitions in MondrianProperties.xml.
*
* @author jhyde
*/
public class PropertyUtil {
/**
* Generates an XML file from a MondrianProperties instance.
*
* @param args Arguments
* @throws IllegalAccessException on error
*/
public static void main0(String[] args) throws IllegalAccessException {
Object properties1 = null; // MondrianProperties.instance();
System.out.println("<PropertyDefinitions>");
for (Field field : properties1.getClass().getFields()) {
org.eigenbase.util.property.Property o =
(org.eigenbase.util.property.Property) field.get(properties1);
System.out.println(" <PropertyDefinition>");
System.out.println(" <Name>" + field.getName() + "</Name>");
System.out.println(" <Path>" + o.getPath() + "</Path>");
System.out.println(
" <Description>" + o.getPath() + "</Description>");
System.out.println(
" <Type>"
+ (o instanceof BooleanProperty ? "boolean"
: o instanceof IntegerProperty ? "int"
: o instanceof DoubleProperty ? "double"
: "String")
+ "</Type>");
if (o.getDefaultValue() != null) {
System.out.println(
" <Default>"
+ o.getDefaultValue() + "</Default" + ">");
}
System.out.println(" </PropertyDefinition>");
}
System.out.println("</PropertyDefinitions>");
}
private static Iterable<Node> iter(final NodeList nodeList) {
return new Iterable<Node>() {
public Iterator<Node> iterator() {
return new Iterator<Node>() {
int pos = 0;
public boolean hasNext() {
return pos < nodeList.getLength();
}
public Node next() {
return nodeList.item(pos++);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**
* Generates MondrianProperties.java from MondrianProperties.xml.
*
* @param args Arguments
*/
public static void main(String[] args)
{
try {
new PropertyUtil().generate(args);
} catch (Throwable e) {
System.out.println("Error while generating properties files.");
e.printStackTrace();
}
}
private void generate(String[] args) {
String olapDir = args.length > 0 ? args[0] : "src/main/java/mondrian/olap";
String outputDir = args.length > 1 ? args[1] : "src/generated/java/mondrian/olap";
final File xmlFile =
new File(olapDir, "MondrianProperties.xml");
final File javaFile =
new File(outputDir, "MondrianProperties.java");
final File propertiesFile =
new File("target/mondrian.properties.template");
final File htmlFile = new File("target/site/doc", "properties.html");
SortedMap<String, PropertyDef> propertyDefinitionMap;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(xmlFile);
Element documentElement = doc.getDocumentElement();
assert documentElement.getNodeName().equals("PropertyDefinitions");
NodeList propertyDefinitions =
documentElement.getChildNodes();
propertyDefinitionMap = new TreeMap<String, PropertyDef>();
for (Node element : iter(propertyDefinitions)) {
if (element.getNodeName().equals("PropertyDefinition")) {
String name = getChildCdata(element, "Name");
String dflt = getChildCdata(element, "Default");
String type = getChildCdata(element, "Type");
String path = getChildCdata(element, "Path");
String category = getChildCdata(element, "Category");
String core = getChildCdata(element, "Core");
String description = getChildCdata(element, "Description");
propertyDefinitionMap.put(
name,
new PropertyDef(
name,
path,
dflt,
category,
PropertyType.valueOf(type.toUpperCase()),
core == null || Boolean.valueOf(core),
description));
}
}
} catch (Throwable e) {
throw new RuntimeException("Error while parsing " + xmlFile, e);
}
doGenerate(Generator.JAVA, propertyDefinitionMap, javaFile);
doGenerate(Generator.HTML, propertyDefinitionMap, htmlFile);
doGenerate(Generator.PROPERTIES, propertyDefinitionMap, propertiesFile);
}
void doGenerate(
Generator generator,
SortedMap<String, PropertyDef> propertyDefinitionMap,
File file)
{
FileWriter fw = null;
PrintWriter out = null;
boolean success = false;
try {
System.out.println("Generating " + file);
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
fw = new FileWriter(file);
out = new PrintWriter(fw);
generator.generate(propertyDefinitionMap, file, out);
out.close();
fw.close();
success = true;
} catch (Throwable e) {
throw new RuntimeException("Error while generating " + file, e);
} finally {
if (out != null) {
out.close();
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
// ignore
}
}
if (!success) {
file.delete();
}
}
}
private static final void printLines(PrintWriter out, String[] lines) {
for (String line : lines) {
out.println(line);
}
}
enum Generator {
JAVA {
@Override
void generate(
SortedMap<String, PropertyDef> propertyDefinitionMap,
File file,
PrintWriter out)
{
out.println("// Generated from MondrianProperties.xml.");
out.println("package mondrian.olap;");
out.println();
out.println("import org.eigenbase.util.property.*;");
out.println("import java.io.File;");
out.println();
printJavadoc(
out, "",
"Configuration properties that determine the\n"
+ "behavior of a mondrian instance.\n"
+ "\n"
+ "<p>There is a method for property valid in a\n"
+ "<code>mondrian.properties</code> file. Although it is possible to retrieve\n"
+ "properties using the inherited {@link java.util.Properties#getProperty(String)}\n"
+ "method, we recommend that you use methods in this class.</p>\n");
String[] lines = {
"public class MondrianProperties extends MondrianPropertiesBase {",
" /**",
" * Properties, drawn from {@link System#getProperties},",
" * plus the contents of \"mondrian.properties\" if it",
" * exists. A singleton.",
" */",
" private static final MondrianProperties instance =",
" new MondrianProperties();",
"",
" private MondrianProperties() {",
" super(",
" new FilePropertySource(",
" new File(mondrianDotProperties)));",
" populate();",
" }",
"",
" /**",
" * Returns the singleton.",
" *",
" * @return Singleton instance",
" */",
" public static MondrianProperties instance() {",
" // NOTE: We used to instantiate on demand, but",
" // synchronization overhead was significant. See",
" // MONDRIAN-978.",
" return instance;",
" }",
"",
};
printLines(out, lines);
for (PropertyDef def : propertyDefinitionMap.values()) {
if (!def.core) {
continue;
}
printJavadoc(out, " ", def.description);
out.println(
" public transient final "
+ def.propertyType.className + " " + def.name + " =");
out.println(
" new " + def.propertyType.className + "(");
out.println(
" this, \"" + def.path + "\", "
+ "" + def.defaultJava() + ");");
out.println();
}
out.println("}");
out.println();
out.println("// End MondrianProperties.java");
}
},
HTML {
@Override
void generate(
SortedMap<String, PropertyDef> propertyDefinitionMap,
File file,
PrintWriter out)
{
out.println("<table>");
out.println(" <tr>");
out.println(" <td><strong>Property</strong></td>");
out.println(" <td><strong>Type</strong></td>");
out.println(" <td><strong>Default value</strong></td>");
out.println(" <td><strong>Description</strong></td>");
out.println(" </tr>");
SortedSet<String> categories = new TreeSet<String>();
for (PropertyDef def : propertyDefinitionMap.values()) {
categories.add(def.category);
}
for (String category : categories) {
out.println(" <tr>");
out.println(
" <td colspan='4'><b><br>" + category + "</b"
+ "></td>");
out.println(" </tr>");
for (PropertyDef def : propertyDefinitionMap.values()) {
if (!def.category.equals(category)) {
continue;
}
out.println(" <tr>");
out.println(
"<td><code><a href='api/mondrian/olap/MondrianProperties.html#"
+ def.name + "'>" + split(def.path)
+ "</a></code></td>");
out.println(
"<td>" + def.propertyType.name() .toLowerCase()
+ "</td>");
out.println(
"<td>" + split(def.defaultHtml()) + "</td>");
out.println(
"<td>" + split(def.description) + "</td>");
out.println(" </tr>");
}
}
out.println("<table>");
}
String split(String s) {
s = s.replaceAll("([,;=.])", "$1");
if (!s.contains("<")) {
s = s.replaceAll("(/)", "$1");
}
return s;
}
},
PROPERTIES {
void generate(
SortedMap<String, PropertyDef> propertyDefinitionMap,
File file,
PrintWriter out)
{
printComments(
out,
"",
"#",
wrapText(
"This software is subject to the terms of the Eclipse Public License v1.0\n"
+ "Agreement, available at the following URL:\n"
+ "http://www.eclipse.org/legal/epl-v10.html.\n"
+ "You must accept the terms of that agreement to use this software.\n"
+ "\n"
+ "Copyright (C) 2001-2005 Julian Hyde\n"
+ "Copyright (C) 2005-2011 Pentaho and others\n"
+ "All Rights Reserved."));
out.println();
char[] chars = new char[79];
Arrays.fill(chars, '#');
String commentLine = new String(chars);
for (PropertyDef def : propertyDefinitionMap.values()) {
out.println(commentLine);
printComments(
out, "", "#", wrapText(stripHtml(def.description)));
out.println("#");
out.println(
"#" + def.path + "="
+ (def.defaultValue == null ? "" : def.defaultValue));
out.println();
}
printComments(out, "", "#", wrapText("End " + file.getName()));
}
};
abstract void generate(
SortedMap<String, PropertyDef> propertyDefinitionMap,
File file,
PrintWriter out);
}
private static void printJavadoc(
PrintWriter out, String prefix, String content)
{
out.println(prefix + "/**");
printComments(out, prefix, " *", wrapText(content));
out.println(prefix + " */");
}
private static void printComments(
PrintWriter out,
String offset,
String prefix,
List<String> strings)
{
for (String line : strings) {
if (line.length() > 0) {
out.println(offset + prefix + " " + line);
} else {
out.println(offset + prefix);
}
}
}
private static String quoteHtml(String s) {
return s.replaceAll("&", "&")
.replaceAll(">", ">")
.replaceAll("<", "<");
}
private static String stripHtml(String s) {
s = s.replaceAll("<li>", "<li>* ");
s = s.replaceAll("<h3>", "<h3>### ");
s = s.replaceAll("</h3>", " ###</h3>");
String[] strings = {
"p", "code", "br", "ul", "li", "blockquote", "h3", "i" };
for (String string : strings) {
s = s.replaceAll("<" + string + "/>", "");
s = s.replaceAll("<" + string + ">", "");
s = s.replaceAll("</" + string + ">", "");
}
s = replaceRegion(s, "{@code ", "}");
s = replaceRegion(s, "{@link ", "}");
s = s.replaceAll("&", "&");
s = s.replaceAll("<", "<");
s = s.replaceAll(">", ">");
return s;
}
private static String replaceRegion(String s, String start, String end) {
int i = 0;
while ((i = s.indexOf(start, i)) >= 0) {
int j = s.indexOf(end, i);
if (j < 0) {
break;
}
s = s.substring(0, i)
+ s.substring(i + start.length(), j)
+ s.substring(j + 1);
i = j - start.length() - end.length();
}
return s;
}
private static List<String> wrapText(String description) {
description = description.trim();
return Arrays.asList(description.split("\n"));
}
private static String getChildCdata(Node element, String name) {
for (Node node : iter(element.getChildNodes())) {
if (node.getNodeName().equals(name)) {
StringBuilder buf = new StringBuilder();
textRecurse(node, buf);
return buf.toString();
}
}
return null;
}
private static void textRecurse(Node node, StringBuilder buf) {
for (Node node1 : iter(node.getChildNodes())) {
if (node1.getNodeType() == Node.CDATA_SECTION_NODE
|| node1.getNodeType() == Node.TEXT_NODE)
{
buf.append(quoteHtml(node1.getTextContent()));
}
if (node1.getNodeType() == Node.ELEMENT_NODE) {
buf.append("<").append(node1.getNodeName()).append(">");
textRecurse(node1, buf);
buf.append("</").append(node1.getNodeName()).append(">");
}
}
}
private static class PropertyDef {
private final String name;
private final String defaultValue;
private final String category;
private final PropertyType propertyType;
private final boolean core;
private final String description;
private final String path;
PropertyDef(
String name,
String path,
String defaultValue,
String category,
PropertyType propertyType,
boolean core,
String description)
{
this.name = name;
this.path = path;
this.defaultValue = defaultValue;
this.category = category == null ? "Miscellaneous" : category;
this.propertyType = propertyType;
this.core = core;
this.description = description;
}
public String defaultJava() {
switch (propertyType) {
case STRING:
if (defaultValue == null) {
return "null";
} else {
return "\"" + defaultValue.replaceAll("\"", "\\\"") + "\"";
}
default:
return defaultValue;
}
}
public String defaultHtml() {
if (defaultValue == null) {
return "-";
}
switch (propertyType) {
case INT:
case DOUBLE:
return new DecimalFormat("#,###.#").format(
new BigDecimal(defaultValue));
default:
return defaultValue;
}
}
}
private enum PropertyType {
INT("IntegerProperty"),
STRING("StringProperty"),
DOUBLE("DoubleProperty"),
BOOLEAN("BooleanProperty");
public final String className;
PropertyType(String className) {
this.className = className;
}
}
}
// End PropertyUtil.java