/* * $Id$ * * Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javatest.util; import java.io.IOException; import java.io.Writer; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; /** * A class that provides a tree of information nodes that can be * selectively printed, suitable for simple command line help. */ public class HelpTree { /** * A node within a HelpTree. A node has a name, a description, * and zero or more child nodes. */ public static class Node { /** * Create a node, with no children. * @param name the name for the node * @param description the description for the node */ public Node(String name, String description) { this.name = name; this.description = description; } /** * Create a node, with given children. * @param name the name for the node * @param description the description for the node * @param children the child nodes for the node */ public Node(String name, String description, Node[] children) { this.name = name; this.description = description; this.children = children; } /** * Create a node, with no children. The name and description are * obtained from a resource bundle, using keys based on a common * prefix. The key for the name will be prefix.name and * the key for the description will be prefix.desc. * @param i18n the resource bundle from which to obtain the * name and description for the node. * @param prefix the prefix for the names of the name and description * entries in the resource bundle. */ public Node(I18NResourceBundle i18n, String prefix) { name = i18n.getString(prefix + ".name"); description = i18n.getString(prefix + ".desc"); } /** * Create a node, with given children. The name and description are * obtained from a resource bundle, using keys based on a common * prefix. The key for the name will be prefix.name and * the key for the description will be prefix.desc. * @param i18n the resource bundle from which to obtain the * name and description for the node. * @param prefix the prefix for the names of the name and description * entries in the resource bundle. * @param children the child nodes for this node */ public Node(I18NResourceBundle i18n, String prefix, Node[] children) { this(i18n, prefix); this.children = children; } /** * Create a node and its children. The name and description are * obtained from a resource bundle, using keys based on a common * prefix. The key for the name will be prefix.name and * the key for the description will be prefix.desc. * The children will each be created with no children of their * own, using a prefix of prefix.entry. * @param i18n the resource bundle from which to obtain the * name and description for the node. * @param prefix the prefix for the names of the name and description * entries in the resource bundle. * @param entries the array of entry names used to create * the child nodes. */ public Node(I18NResourceBundle i18n, String prefix, String[] entries) { this(i18n, prefix); children = new Node[entries.length]; for (int i = 0; i < children.length; i++) children[i] = new Node(i18n, prefix + '.' + entries[i]); } /** * Get the name of this node. * @return the name of this node */ public final String getName() { return name; } /** * Get the description of this node. * @return the description of this node */ public final String getDescription() { return description; } /** * Get the number of children of this node. * @return the number of children of this node */ public int getChildCount() { return (children == null ? 0 : children.length); } /** * Get a specified child of this node. * @param i the index of the desired child * @return the specified child of this node */ public Node getChild(int i) { if (i >= getChildCount()) throw new IllegalArgumentException(); return children[i]; } private String name; private String description; private Node[] children; } /** * A selection of nodes within a HelpTree. * @see HelpTree#find */ public class Selection { private Selection(Node node) { this(node, null); } private Selection(Map map) { this(null, map); } private Selection(Node node, Map map) { this.node = node; this.map = map; } private Node node; private Map map; } /** * Create an empty HelpTree object. */ public HelpTree() { nodes = new Node[0]; } /** * Create a HelpTree object containing a given set of nodes. * @param nodes the contents of the HelpTree */ public HelpTree(Node[] nodes) { this.nodes = nodes; } /** * Add a node to a help tree. * @param node the node to be added to the tree */ public void addNode(Node node) { nodes = DynamicArray.append(nodes, node); } /** * Get the indentation used to adjust the left margin when writing * the child nodes for a node. * @return the indentation used to adjust the left margin when writing * the child nodes for a node * @see #setNodeIndent */ public int getNodeIndent() { return nodeIndent; } /** * Set the indentation used to adjust the left margin when writing * the child nodes for a node. * @param n the indentation used to adjust the left margin when writing * the child nodes for a node * @see #getNodeIndent */ public void setNodeIndent(int n) { nodeIndent = n; } /** * Get the indentation used to adjust the left margin when writing * the description of a node. * @return the indentation used to adjust the left margin when writing * the description of a node * @see #setDescriptionIndent */ public int getDescriptionIndent() { return descriptionIndent; } /** * Set the indentation used to adjust the left margin when writing * the description of a node. * @param n the indentation used to adjust the left margin when writing * the description of a node * @see #getDescriptionIndent */ public void setDescriptionIndent(int n) { descriptionIndent = n; } /** * Get a selection representing the nodes that match the given words. * If there are nodes whose name or description contain all of the * given words, then those nodes will be returned. * Otherwise, all nodes whose name or description contain at least one * of the given words will be returned. * @param words the words to be searched for * @return a Selection containing the matching nodes */ public Selection find(String[] words) { Selection s = find(words, ALL); if (s == null && words.length > 1) s = find(words, ANY); return s; } /** * Get a selection representing the nodes that match all of the given words. * @param words the words to be searched for * @return a Selection containing the matching nodes */ public Selection findAll(String[] words) { return find(words, ALL); } /** * Get a selection representing the nodes that each match * at least one of the given words. * @param words the words to be searched for * @return a Selection containing the matching nodes */ public Selection findAny(String[] words) { return find(words, ANY); } private Selection find(String[] words, int mode) { Map map = null; for (int i = 0; i < nodes.length; i++) { Node node = nodes[i]; Selection s = find(node, words, mode); if (s != null) { if (map == null) map = new TreeMap<>(nodeComparator); map.put(node, s); } } return (map == null ? null : new Selection(map)); } private Selection find(Node node, String[] words, int mode) { if (mode == ALL) { if (containsAllOf(node.name, words) || containsAllOf(node.description, words)) return new Selection(node); } else if (mode == ANY) { if (containsAnyOf(node.name, words) || containsAnyOf(node.description, words)) return new Selection(node); } else throw new IllegalArgumentException(); if (node.children == null) return null; Map map = null; for (int i = 0; i < node.children.length; i++) { Node child = node.children[i]; Selection s = find(child, words, mode); if (s != null) { if (map == null) map = new TreeMap<>(nodeComparator); map.put(child, s); } } return (map == null ? null : new Selection(node, map)); } /** * Write out all the nodes in this HelpTree. * @param out the writer to which to write the nodes. * If out is a com.sun.javatest.util.WrapWriter, it will be * used directly, otherwise a WrapWriter will be created * that will write to the given writer. * @throws IOException if the is a problem writing the * nodes. * @see WrapWriter */ public void write(Writer out) throws IOException { WrapWriter ww = getWrapWriter(out); for (int i = 0; i < nodes.length; i++) { write(ww, nodes[i]); ww.write('\n'); } if (ww != out) ww.flush(); } /** * Write out selected nodes in this HelpTree. * @param out the writer to which to write the nodes. * If out is a com.sun.javatest.util.WrapWriter, it will be * used directly, otherwise a WrapWriter will be created * that will write to the given writer. * @param s a Selection object containing the nodes to be written * @throws IOException if the is a problem writing the * nodes. * @see WrapWriter */ public void write(Writer out, Selection s) throws IOException { WrapWriter ww = getWrapWriter(out); write(ww, s.map); if (ww != out) ww.flush(); } /** * Write out a summary of all the nodes in this HelpTree. * The summary will contain the name and description of the * top level nodes, but not any of their children. * @param out the writer to which to write the nodes. * If out is a com.sun.javatest.util.WrapWriter, it will be * used directly, otherwise a WrapWriter will be created * that will write to the given writer. * @throws IOException if the is a problem writing the * nodes. * @see WrapWriter */ public void writeSummary(Writer out) throws IOException { WrapWriter ww = getWrapWriter(out); for (int i = 0; i < nodes.length; i++) writeHead(ww, nodes[i]); if (ww != out) ww.flush(); } /** * Sets the comparator which will be used in {@link #find(String[]) find} methods * method * @param comparator Comparator to set */ public void setNodeComparator(Comparator comparator){ nodeComparator = comparator; } /** * Returns current comparator used in {@link #find(String[]) find} methods * method * @return current node comparator */ public Comparator getNodeComparator(){ return nodeComparator; } private void write(WrapWriter out, Map m) throws IOException { int margin = out.getLeftMargin(); for (Iterator> iter = m.entrySet().iterator(); iter.hasNext(); ) { Map.Entry e = iter.next(); Node node = (e.getKey()); Selection s = (e.getValue()); if (s.map == null) write(out, node); else { writeHead(out, node); out.setLeftMargin(margin + nodeIndent); write(out, s.map); out.setLeftMargin(margin); } if (margin == 0) out.write('\n'); } } private void write(WrapWriter out, Node node) throws IOException { int baseMargin = out.getLeftMargin(); writeHead(out, node); Node[] children = node.children; if (children != null && children.length > 0) { out.setLeftMargin(baseMargin + nodeIndent); for (int i = 0; i < children.length; i++) write(out, children[i]); } out.setLeftMargin(baseMargin); } private void writeHead(WrapWriter out, Node node) throws IOException { int baseMargin = out.getLeftMargin(); String name = node.name; String desc = node.description; if (name != null) { out.write(name); out.write(' '); if (desc != null) { out.setLeftMargin(baseMargin + descriptionIndent); if (out.getCharsOnLineSoFar() + 2 > out.getLeftMargin()) out.write('\n'); out.write(desc); } out.write('\n'); } out.setLeftMargin(baseMargin); } private boolean containsAllOf(String text, String[] words) { for (int i = 0; i < words.length; i++) { if (!contains(text, words[i])) return false; } return true; } private boolean containsAnyOf(String text, String[] words) { for (int i = 0; i < words.length; i++) { if (contains(text, words[i])) return true; } return false; } private boolean contains(String text, String word) { int startIndex = text.toLowerCase().indexOf(word.toLowerCase()); if (startIndex == -1) return false; int endIndex = startIndex + word.length(); return ((startIndex == 0 || !Character.isLetter(text.charAt(startIndex - 1))) && (endIndex == text.length() || !Character.isLetter(text.charAt(endIndex)))); } private WrapWriter getWrapWriter(Writer out) { return (out instanceof WrapWriter ? (WrapWriter) out : new WrapWriter(out)); } private Node[] nodes; private int nodeIndent = 4; private int descriptionIndent = 16; private static final int ALL = 1; private static final int ANY = 2; private Comparator nodeComparator = new Comparator() { public int compare(Node n1, Node n2) { int v = compareStrings(n1.name, n2.name); return (v != 0 ? v : compareStrings(n1.description, n2.description)); } private int compareStrings(String s1, String s2) { if (s1 == null && s2 == null) return 0; if (s1 == null || s2 == null) return (s1 == null ? -1 : +1); return s1.toLowerCase().compareTo(s2.toLowerCase()); } }; }