1 /*
2  * This file is part of ELKI:
3  * Environment for Developing KDD-Applications Supported by Index-Structures
4  *
5  * Copyright (C) 2018
6  * ELKI Development Team
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Affero General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Affero General Public License for more details.
17  *
18  * You should have received a copy of the GNU Affero General Public License
19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 package de.lmu.ifi.dbs.elki.gui.util;
22 
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.List;
26 
27 import javax.swing.tree.DefaultMutableTreeNode;
28 import javax.swing.tree.MutableTreeNode;
29 import javax.swing.tree.TreeNode;
30 
31 import de.lmu.ifi.dbs.elki.utilities.ELKIServiceScanner;
32 
33 /**
34  * Build a tree of available classes for use in Swing UIs.
35  *
36  * @author Erich Schubert
37  * @since 0.7.0
38  *
39  * @has - - - TreeNode
40  * @composed - - - PackageNode
41  * @composed - - - ClassNode
42  */
43 public final class ClassTree {
44   /**
45    * Private constructor. Static methods only.
46    */
ClassTree()47   private ClassTree() {
48     // Do not use.
49   }
50 
51   /**
52    * Build the class tree for a given set of choices.
53    *
54    * @param choices Class choices
55    * @param rootpkg Root package name (to strip / hide)
56    * @return Root node.
57    */
build(List<Class<?>> choices, String rootpkg)58   public static TreeNode build(List<Class<?>> choices, String rootpkg) {
59     MutableTreeNode root = new PackageNode(rootpkg, rootpkg);
60     HashMap<String, MutableTreeNode> lookup = new HashMap<>();
61     if(rootpkg != null) {
62       lookup.put(rootpkg, root);
63     }
64     lookup.put("de.lmu.ifi.dbs.elki", root);
65     lookup.put("", root);
66 
67     // Use the shorthand version of class names.
68     String prefix = rootpkg != null ? rootpkg + "." : null;
69 
70     Class<?>[] choic = choices.toArray(new Class<?>[choices.size()]);
71     Arrays.sort(choic, ELKIServiceScanner.SORT_BY_PRIORITY);
72     for(Class<?> impl : choic) {
73       String name = impl.getName();
74       name = (prefix != null && name.startsWith(prefix)) ? name.substring(prefix.length()) : name;
75       int plen = (impl.getPackage() != null) ? impl.getPackage().getName().length() + 1 : 0;
76       MutableTreeNode c = new ClassNode(impl.getName().substring(plen), name);
77 
78       MutableTreeNode p = null;
79       int l = name.lastIndexOf('.');
80       while(p == null) {
81         if(l < 0) {
82           p = root;
83           break;
84         }
85         String pname = name.substring(0, l);
86         p = lookup.get(pname);
87         if(p != null) {
88           break;
89         }
90         l = pname.lastIndexOf('.');
91         MutableTreeNode tmp = new PackageNode(l >= 0 ? pname.substring(l + 1) : pname, pname);
92         tmp.insert(c, 0);
93         c = tmp;
94         lookup.put(pname, tmp);
95         name = pname;
96       }
97       p.insert(c, p.getChildCount());
98     }
99     // Simplify tree, except for root node
100     for(int i = 0; i < root.getChildCount(); i++) {
101       MutableTreeNode c = (MutableTreeNode) root.getChildAt(i);
102       MutableTreeNode c2 = simplifyTree(c, null);
103       if(c != c2) {
104         root.remove(i);
105         root.insert(c2, i);
106       }
107     }
108     return root;
109   }
110 
111   /**
112    * Simplify the tree.
113    *
114    * @param cur Current node
115    * @param prefix Prefix to add
116    * @return Replacement node
117    */
simplifyTree(MutableTreeNode cur, String prefix)118   private static MutableTreeNode simplifyTree(MutableTreeNode cur, String prefix) {
119     if(cur instanceof PackageNode) {
120       PackageNode node = (PackageNode) cur;
121       if(node.getChildCount() == 1) {
122         String newprefix = (prefix != null) ? prefix + "." + (String) node.getUserObject() : (String) node.getUserObject();
123         cur = simplifyTree((MutableTreeNode) node.getChildAt(0), newprefix);
124       }
125       else {
126         if(prefix != null) {
127           node.setUserObject(prefix + "." + (String) node.getUserObject());
128         }
129         for(int i = 0; i < node.getChildCount(); i++) {
130           MutableTreeNode c = (MutableTreeNode) node.getChildAt(i);
131           MutableTreeNode c2 = simplifyTree(c, null);
132           if(c != c2) {
133             node.remove(i);
134             node.insert(c2, i);
135           }
136         }
137       }
138     }
139     else if(cur instanceof ClassNode) {
140       ClassNode node = (ClassNode) cur;
141       if(prefix != null) {
142         node.setUserObject(prefix + "." + (String) node.getUserObject());
143       }
144     }
145     return cur;
146   }
147 
148   /**
149    * Tree node representing a single class.
150    *
151    * @author Erich Schubert
152    */
153   public static class PackageNode extends DefaultMutableTreeNode {
154     /**
155      * Serial version
156      */
157     private static final long serialVersionUID = 1L;
158 
159     /**
160      * Class name.
161      */
162     private String pkgname;
163 
164     /**
165      * Current class name.
166      *
167      * @param display Displayed name
168      * @param pkgname Actual class name
169      */
PackageNode(String display, String pkgname)170     public PackageNode(String display, String pkgname) {
171       super(display);
172       this.pkgname = pkgname;
173     }
174 
175     /**
176      * Return the package name.
177      *
178      * @return Package name
179      */
getPackageName()180     public String getPackageName() {
181       return pkgname;
182     }
183   }
184 
185   /**
186    * Tree node representing a single class.
187    *
188    * @author Erich Schubert
189    */
190   public static class ClassNode extends DefaultMutableTreeNode {
191     /**
192      * Serial version
193      */
194     private static final long serialVersionUID = 1L;
195 
196     /**
197      * Class name.
198      */
199     private String clsname;
200 
201     /**
202      * Current class name.
203      *
204      * @param display Displayed name
205      * @param clsname Actual class name
206      */
ClassNode(String display, String clsname)207     public ClassNode(String display, String clsname) {
208       super(display);
209       this.clsname = clsname;
210     }
211 
212     /**
213      * Return the class name.
214      *
215      * @return Class name
216      */
getClassName()217     public String getClassName() {
218       return clsname;
219     }
220   }
221 }
222