/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2001-2003, 2006, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 USA * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * + Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************************/ package net.sourceforge.cruisecontrol.gendoc.html; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.Properties; import net.sourceforge.cruisecontrol.gendoc.ChildInfo; import net.sourceforge.cruisecontrol.gendoc.PluginInfo; import org.apache.log4j.Logger; /** * Contains utilities for writing out the HTML for the plugin page. * @author Seth Pollen (pollens@msoe.edu) */ public class HtmlUtils { private static final Logger LOG = Logger.getLogger(HtmlUtils.class); /** Line count for the # lines in the generated hierarchical TOC*/ private int count; /** The release version Information */ private final String releaseVersion; public HtmlUtils() { final String tmpVersion = readVerFromManifest(); if (tmpVersion != null) { releaseVersion = tmpVersion; } else { releaseVersion = readVerFromBuildProps(); } } private static String readVerFromBuildProps() { // Should only need this when running from source tree. Search upwards // for build.properties. String fileName = "build.properties"; File dir = new File(System.getProperty("user.dir")); File buildPropFile; while (true) { buildPropFile = new File(dir, fileName); if (buildPropFile.exists()) { break; } else { // Move up one level in the directory structure and check again. dir = dir.getParentFile(); if (dir == null) { // We have run out of places to search. LOG.debug("Build props not found."); return "UNKNOWN"; } } } final Properties buildProps = new Properties(); try { // @todo Restore use of Reader after minimum JDK >= 1.6. //buildProps.load(new BufferedReader(new FileReader(buildPropFile))); buildProps.load(new BufferedInputStream(new FileInputStream(buildPropFile))); } catch (FileNotFoundException e) { LOG.debug("Build props not found.", e); return "UNKNOWN"; } catch (IOException e) { LOG.debug("IOError reading build props.", e); return "UNKNOWN"; } return buildProps.getProperty("cc.version"); } private static String readVerFromManifest() { // This should work when running from jars. final Package pkg = HtmlUtils.class.getPackage(); if (pkg != null) { return pkg.getImplementationVersion(); } return null; } public String getReleaseVersion() { return releaseVersion; } /** * Method that generates the Hierarchical TOC * * @param info The PluginInfo tree * @return The HTML generated for the hierarchical TOC */ public String generateHierarchyToc(PluginInfo info) { StringWriter w = new StringWriter(); try { writePluginInfo(info, null, w, 0, " "); } catch (IOException e) { e.printStackTrace(); } return w.getBuffer().toString(); } /** * Writes the appropriate HTML to the given Writer * * @param node The current node * @param parent The node who this node is a child of (null if root) * @param w The Writer to write to * @param depth The current depth within the tree * @param indentationStr The indentation ammount to use * @throws IOException In case of Writer error */ private void writePluginInfo(PluginInfo node, PluginInfo parent, Writer w, int depth, String indentationStr) throws IOException { if (parent == null || node.getDirectParent() == parent) { // Skip repeated children (null means root of tree) count++; String nodeName = node.getName(); indent(w, depth, indentationStr); w.append("<a href=\"#"); w.append(node.getAncestralName()); w.append("\"><"); w.append(nodeName); if (node.getChildren().isEmpty()) { w.append("/"); } w.append("></a>\n"); List<PluginInfo> childrenToPrint = getChildrenToPrint(node); for (PluginInfo pi : childrenToPrint) { writePluginInfo(pi, node, w, depth + 1, indentationStr); } if (!childrenToPrint.isEmpty()) { count++; indent(w, depth, indentationStr); w.append("<").append("/").append(nodeName).append(">\n"); } } } /** * Returns a List of all Nodes to be displayed under a given node in the * hierarchical TOC. * * @param node The Node whose children are to be checked * @return The List containing all nodes to be displayed. This may be an * empty set. */ private List<PluginInfo> getChildrenToPrint(PluginInfo node) { // To make sure each node appears in the TOC only once, only return the // nodes that consider this node to be their one and only direct parent. List<PluginInfo> list = new ArrayList<PluginInfo>(); for (ChildInfo ci : node.getChildren()) { for (PluginInfo pi : ci.getAllowedNodes()) { if (pi.getDirectParent() == node) { list.add(pi); } } } return list; } /** * Indents the writer by the appropriate amount * * @param w The Writer to indent * @param depth The current depth * @param indentationChar The indentation amount * @throws IOException In case of Writer error */ private void indent(Writer w, int depth, String indentationChar) throws IOException { for (int i = 0; i < depth; i++) { w.append(indentationChar); } } /** * Method that generates the Alphabetical TOC * * @param info The PluginInfo tree * @return The HTML generated for the Alphabetical TOC */ public String generateAlphabeticalToc(List<PluginInfo> info) { StringBuilder toc = new StringBuilder(); for (PluginInfo pi : info) { toc .append("<a href=\"#") .append(pi.getAncestralName()) .append("\"><") .append(pi.getName()) .append("></a>\n"); } return toc.toString(); } /** * Returns the number of lines in the hierarchical TOC * // @todo Not used. Make package visible, private, or remove? * @return The number of lines in the hierarchical TOC */ public int getLineCount() { return count; } /** * Converts null Strings to empty Strings. * @param str Input string, which may be null. * @return Output string, which will be "" if the input was null. */ public static String emptyIfNull(String str) { return (str == null) ? "" : str; } }