/* * This file is part of Alida, a Java library for * Advanced Library for Integrated Development of Data Analysis Applications. * * Copyright (C) 2010 - @YEAR@ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Fore more information on Alida, visit * * http://www.informatik.uni-halle.de/alida/ * */ package de.unihalle.informatik.Alida.gui; import javax.swing.*; import javax.swing.tree.*; import java.util.*; import java.io.BufferedReader; import java.io.FileReader; import de.unihalle.informatik.Alida.annotations.*; import de.unihalle.informatik.Alida.helpers.ALDEnvironmentConfig; import de.unihalle.informatik.Alida.operator.ALDOperatorLocation; /** * This class extends <code>JTree</code> to hold <code>ALDOperator</code>s to choose from. * There are two levels which determine the operators available, namely * the standard and application level. * <p> * The initial level of operators displayed is by default the application level. * This may be overridden by a JVM property alida_oprunner_level or * the environment variable ALIDA_OPRUNNER_LEVEL. * <p> * A list of operators to be unfolded at start up may be supplied in one or more files. * Each file is assumed to contain one operator name per line. * The name(s) of this file(s) may be specified by a JVM property alida_oprunner_favoriteops or * the environment variable ALIDA_OPRUNNER_FAVORITEOPS as a colon separated list of filenames. * <p> * Optionally a string to filter operators (and workflows) may be supplied via * <code>opNameFilter</code>. If null, all annotated operators will we displayed. * If non null and this string contains only alpha numeric letters and dots, * all operators containing the filter string will be displayed where matching * is performed case insensitive. * Otherwise the filter string is interpreted as a regular expression and matching is * performed case sensitive. * * @author Stefan Posch */ public class ALDOperatorChooserTree extends JTree { /** * Debug flag (not accessible from outside). */ static boolean debug = false; /** * Mode of usage. * <p> * In application mode only operators annotated as applications are * available, otherwise all annotated operators are displayed. */ protected ALDAOperator.Level level = ALDAOperator.Level.APPLICATION; /** * List of favorite operators to be unfolded at startup. */ Vector<String> favoriteOperators; /** * Tree of available operators for standard level. */ protected DefaultTreeModel standardTreeModel; /** * Tree of available operators for standard level. */ protected DefaultTreeModel applicationTreeModel; private Collection<ALDOperatorLocation> standardLocations; private Collection<ALDOperatorLocation> applicationLocations; /** * if non null regular expression select operators */ private String opNameFilter = null; /** * Constructor. */ public ALDOperatorChooserTree( Collection<ALDOperatorLocation> standardLocations, Collection<ALDOperatorLocation> applicationLocations) { if ( ALDOperatorChooserTree.debug ) { System.out.println( "ALDOperatorChooserTree::ALDOperatorChooserTree"); } this.standardLocations = standardLocations; this.applicationLocations = applicationLocations; // read configuration this.favoriteOperators = initFavoriteOperators(); this.standardTreeModel = createOpTreeModel( standardLocations, "ALDOperators"); this.applicationTreeModel = createOpTreeModel( applicationLocations, "ALDOperators"); String levelString = ALDEnvironmentConfig.getConfigValue("OPRUNNER","LEVEL"); if (levelString == null || levelString.equalsIgnoreCase("application") ) { this.level = ALDAOperator.Level.APPLICATION; this.setModel( applicationTreeModel); } else { this.level = ALDAOperator.Level.STANDARD; this.setModel( standardTreeModel); } hideLeaves( new TreePath(this.getModel().getRoot())); } /** Return current mode of usage. * * @return current mode */ public ALDAOperator.Level getLevel() { return level; } /** * Set the level of the operators in this tree. * Keep the current expansion state of the tree. * * @param level new level */ public void setLevel( ALDAOperator.Level level) { if ( ALDOperatorChooserTree.debug ) { System.out.println( "ALDOperatorChooserTree::setLevel to " + level); } this.level = level; HashSet<String> expandedPackageNames = this.collectExpandedPackages( new TreePath(this.getModel().getRoot()), ""); if ( debug ) { for ( String str : expandedPackageNames) { System.out.println("ALDOperatorChooserTree::setLevel unfolded package: " + str); } } if ( level == ALDAOperator.Level.STANDARD) { this.setModel( standardTreeModel); } else { this.setModel( applicationTreeModel); } this.makeVisibleNodes(new TreePath(this.getModel().getRoot()), "", expandedPackageNames); this.updateUI(); } /** * @return the opNameFilter */ public String getOpNameFilter() { return opNameFilter; } /** * @param opNameFilter the opNameFilter to set */ public void setOpNameFilter(String opNameFilter) { if ( opNameFilter.isEmpty()) this.opNameFilter = null; else this.opNameFilter = opNameFilter; if (level == ALDAOperator.Level.APPLICATION) { this.applicationTreeModel = createOpTreeModel( applicationLocations, "ALDOperators"); this.setModel( applicationTreeModel); } else { this.standardTreeModel = createOpTreeModel( standardLocations, "ALDOperators"); this.setModel( standardTreeModel); } if ( opNameFilter.isEmpty()) hideLeaves( new TreePath(this.getModel().getRoot())); else unhideLeaves( new TreePath(this.getModel().getRoot())); this.updateUI(); } @Override public ALDOperatorChooserTreeNode getLastSelectedPathComponent() { return (ALDOperatorChooserTreeNode) super.getLastSelectedPathComponent(); } private DefaultTreeModel createOpTreeModel( Collection<ALDOperatorLocation> operators, String nameOfRootNode) { if (ALDOperatorChooserTree.debug) System.out.println("ALDOperatorChooserTree::createOpTreeeModel " + "Looking up all annotated @ALDAOperators for level " + level); // first we build the tree with our own data structure AuxNode treeOfOpNames = new AuxNode(); for (ALDOperatorLocation location : operators) { String className = location.getName(); if ( opNameFilter != null && ! this.matches(className, opNameFilter)) { if ( ALDOperatorChooserTree.debug) { System.out.println("ALDOperatorChooserTree::createOpTreeModel do not add " + className); } continue; } if ( ALDOperatorChooserTree.debug) { System.out.println("ALDOperatorChooserTree::createOpTreeModel adding " + className); } // build a tree of opNames according to packages String[] parts = location.getPartsOfName(); AuxNode currentNode = treeOfOpNames; // loop over the part but the last one int i; for (i = 0; i < parts.length; i++) { String part = parts[i]; if (currentNode.children.get(part) == null) { AuxNode newChild = new AuxNode(null); currentNode.children.put(part, newChild); } currentNode = currentNode.children.get(part); if ( i == (parts.length-1)) { currentNode.location = location; } } } ALDOperatorChooserTreeNode node = treeOfOpNames.createTree(nameOfRootNode); return new DefaultTreeModel(node); } /** Does <code>className</code> match the opNameFilter * @param className * @param opNameFilter * @return */ private boolean matches(String className, String opNameFilter) { if ( opNameFilter.matches("^[a-zA-Z.]*$") ) { return className.toLowerCase().matches(".*"+opNameFilter.toLowerCase() + ".*"); } else { return className.matches(opNameFilter); } } /** * Hide all leaves of this tree below the given path. * The exception to this rule is that all favorite operators are unfolded * which also unfolds there siblings. * * @param treePath Path below which leaves are to be hidden. */ private void hideLeaves( TreePath treePath) { ALDOperatorChooserTreeNode lastNode = (ALDOperatorChooserTreeNode) treePath.getLastPathComponent(); if (!lastNode.isLeaf()) this.collapsePath(treePath); if ( lastNode.getLocation() != null) { if (this.favoriteOperators.contains(lastNode.getLocation().getName())) { this.makeVisible(treePath); } } TreeModel treeModel = this.getModel(); if (treeModel.getChildCount(lastNode) >= 0) { for (int i = 0; i < treeModel.getChildCount(lastNode); i++) { TreeNode child = (TreeNode) treeModel.getChild(lastNode, i); TreePath extendedPath = treePath.pathByAddingChild(child); hideLeaves( extendedPath); } } } /** * Unhide all leaves of this tree below the given path. * * @param treePath Path below which leaves are to be hidden. */ private void unhideLeaves( TreePath treePath) { ALDOperatorChooserTreeNode lastNode = (ALDOperatorChooserTreeNode) treePath.getLastPathComponent(); this.makeVisible(treePath); TreeModel treeModel = this.getModel(); if (treeModel.getChildCount(lastNode) >= 0) { for (int i = 0; i < treeModel.getChildCount(lastNode); i++) { TreeNode child = (TreeNode) treeModel.getChild(lastNode, i); TreePath extendedPath = treePath.pathByAddingChild(child); unhideLeaves( extendedPath); } } } /** Collect the package names (for operators) and path (for workflows) which are * currently expanded. * Use a <code>+</code> to separate the parts. * * @param treePath * @param base * @return */ private HashSet<String> collectExpandedPackages(TreePath treePath, String base) { HashSet<String> res = null; ALDOperatorChooserTreeNode lastNode = (ALDOperatorChooserTreeNode) treePath.getLastPathComponent(); if ( this.isExpanded(treePath)) { res = new HashSet<String>(); String name = new String(); if ( String.class.isAssignableFrom(lastNode.getUserObject().getClass()) ) name = (String)lastNode.getUserObject(); String packageName; if ( base.isEmpty() ) packageName = name; else packageName = base + "+" + name; res.add(packageName); TreeModel treeModel = this.getModel(); if (treeModel.getChildCount(lastNode) >= 0) { for (int i = 0; i < treeModel.getChildCount(lastNode); i++) { TreeNode child = (TreeNode) treeModel.getChild(lastNode, i); TreePath extendedPath = treePath.pathByAddingChild(child); HashSet<String> recursiveResult = collectExpandedPackages( extendedPath, packageName); if (recursiveResult != null) { res.addAll(recursiveResult); } } } } return res; } /** * Makes all nodes of the tree visible where the corresponding package names (of workflow paths) * are contained in <code>packageNames</code>. * * @param treePath * @param base * @param packageNames * @return */ private void makeVisibleNodes(TreePath treePath, String base, HashSet<String> packageNames) { ALDOperatorChooserTreeNode lastNode = (ALDOperatorChooserTreeNode) treePath.getLastPathComponent(); String name = new String(); if ( String.class.isAssignableFrom(lastNode.getUserObject().getClass()) ) name = (String)lastNode.getUserObject(); String packageName; if ( base.isEmpty() ) packageName = name; else packageName = base + "+" + name; if ( packageNames.contains(packageName)) { if ( debug ) { System.out.println( "ALDOperatorChooserTree::makeVisibleNodes make visible " + packageName); } this.expandPath(treePath); TreeModel treeModel = this.getModel(); if (treeModel.getChildCount(lastNode) >= 0) { for (int i = 0; i < treeModel.getChildCount(lastNode); i++) { TreeNode child = (TreeNode) treeModel.getChild(lastNode, i); TreePath extendedPath = treePath.pathByAddingChild(child); makeVisibleNodes( extendedPath, packageName, packageNames); } } } else { if ( debug ) { System.out.println( "ALDOperatorChooserTree::makeVisibleNodes make invisible " + packageName); } this.collapsePath(treePath); } } /** * Initialize list of favorite operators to be unfolded at startup. * <p> * The list of operators to be unfolded at start up may be supplied in one or more files. * Each file is assumed to contain one operator name per line. * The name(s) of this file(s) may be specified by a JVM property alida_oprunner_favoriteops or * the environment variable ALIDA_OPRUNNER_FAVORITEOPS as a colon separated list of filenames. * * @return List of favorite operators. */ private Vector<String> initFavoriteOperators() { String favoriteOpsConfigFiles = ALDEnvironmentConfig.getConfigValue( "OPRUNNER", "FAVORITEOPS"); if ( favoriteOpsConfigFiles == null) { favoriteOpsConfigFiles = System.getProperty("user.home") + "/.alida/favoriteops"; } Vector<String> favoriteOps = new Vector<String>(); if (ALDOperatorChooserTree.debug) { System.out.println("ALDChooseOpNameFrame::initFavoriteOperators configfile = " + favoriteOpsConfigFiles); } if (favoriteOpsConfigFiles != null) { for ( String filename : favoriteOpsConfigFiles.split(":")) try { BufferedReader reader = new BufferedReader(new FileReader( filename)); String line; while ((line = reader.readLine()) != null) { favoriteOps.add(line); if ( ALDOperatorChooserTree.debug ) { System.out.println(" added favorite operator " + line); } } } catch (Exception e) { // nothing to be done } } return favoriteOps; } // ======================================================================================== /** * Node to build up a tree of the package structure of <code>ALDOperator</code>s. * Useful as we have a hash map of children. */ private class AuxNode { /** * Children of this node. */ HashMap<String, AuxNode> children; /** * location object for this node. Is null for inner nodes, i.e. nodes not representing an operator */ ALDOperatorLocation location; /** * Constructor. * * @param fullName Full name of root operator. */ AuxNode(ALDOperatorLocation location) { this.children = new HashMap<String, AuxNode>(); this.location = location; } /** * Default constructor. */ AuxNode() { this( null); } /** * Generates the ModelTree using the tree represented with <code>AuxNode</code>s. * * @param name name of node. * @return Created tree. */ ALDOperatorChooserTreeNode createTree(String name) { // create a TreeNode for this AuxName ALDOperatorChooserTreeNode node = new ALDOperatorChooserTreeNode(name, this.location); LinkedList<String> keysSorted = new LinkedList<String>( this.children.keySet()); java.util.Collections.sort(keysSorted); for (String str : keysSorted) { ALDOperatorChooserTreeNode child = this.children.get(str).createTree(str); node.add(child); } return node; } } }