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("([,;=.])", "&shy;$1&shy;");
321                if (!s.contains("<")) {
322                    s  = s.replaceAll("(/)", "&shy;$1&shy;");
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("&", "&amp;")
398            .replaceAll(">", "&gt;")
399            .replaceAll("<", "&lt;");
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("&amp;", "&");
416        s = s.replaceAll("&lt;", "<");
417        s = s.replaceAll("&gt;", ">");
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