Java tutorial
/* * Copyright (c) 2003 Frank Sauer. All rights reserved. Licenced under CPL 1.0 * (Common Public License Version 1.0). The licence is available at * http://www.eclipse.org/legal/cpl-v10.html. DISCLAIMER OF WARRANTIES AND * LIABILITY: THE SOFTWARE IS PROVIDED "AS IS". THE AUTHOR MAKES NO * REPRESENTATIONS OR WARRANTIES, EITHER EXPRESS OR IMPLIED. TO THE EXTENT NOT * PROHIBITED BY LAW, IN NO EVENT WILL THE AUTHOR BE LIABLE FOR ANY DAMAGES, * INCLUDING WITHOUT LIMITATION, LOST REVENUE, PROFITS OR DATA, OR FOR SPECIAL, * INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO ANY * FURNISHING, PRACTICING, MODIFYING OR ANY USE OF THE SOFTWARE, EVEN IF THE * AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. $Id: * MetricsTable.java,v 1.36 2003/06/14 03:45:13 sauerf Exp $ */ package net.sourceforge.metrics.ui.layeredpackagegraph; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import net.sourceforge.metrics.core.Avg; import net.sourceforge.metrics.core.Constants; import net.sourceforge.metrics.core.Log; import net.sourceforge.metrics.core.Max; import net.sourceforge.metrics.core.Metric; import net.sourceforge.metrics.core.MetricDescriptor; import net.sourceforge.metrics.core.MetricsPlugin; import net.sourceforge.metrics.core.sources.AbstractMetricSource; import net.sourceforge.metrics.core.sources.IGraphContributor; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.preference.PreferenceConverter; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.TableTree; import org.eclipse.swt.custom.TableTreeItem; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.TreeEvent; import org.eclipse.swt.events.TreeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IMemento; import org.eclipse.ui.PartInitException; /** * TableTree specialized for metrics display. Specializations include lazy child creation and child sorting in descending metric value * * @author Frank Sauer */ public class LayeredPackageTable extends TableTree implements Constants, SelectionListener, TreeListener { private Color lastDefaultColor; private Color lastInRangeColor; private Color lastOutofRangeColor; private TableColumn layer; private TableColumn value; private TableColumn dependencies; private Map<String, Set<String>> deps; private static List<Set<PackageStats>> layers; private static Set<PackageStats> external; public LayeredPackageTable(Composite parent, int style) { super(parent, style); getTable().setLinesVisible(true); getTable().setHeaderVisible(true); layer = new TableColumn(getTable(), SWT.RIGHT); layer.setText("Layer"); value = new TableColumn(getTable(), SWT.LEFT); value.setText("Package"); dependencies = new TableColumn(getTable(), SWT.LEFT); dependencies.setText("Dependent Packages"); addSelectionListener(this); addTreeListener(this); } /** * Update the table with new dependencies. */ public void setMetrics(final AbstractMetricSource ms) { try { removeAll(); // TODO: Allow for statistics for one package only if (!(ms instanceof IGraphContributor)) { return; } deps = ((IGraphContributor) ms).getEfferent(); if (deps != null) { external = new TreeSet<PackageStats>(); calculateLayers(deps, external); displayInternalPackages(deps, layers); displayExternalPackages(external); } } catch (Throwable e) { Log.logError("MetricsTable::setMetrics", e); e.printStackTrace(); } } private void displayExternalPackages(Set<PackageStats> external) { TableTreeItem divider = createNewRow(); divider.setText(0, "EXTERNAL"); divider.setText(1, ""); divider.setText(2, ""); for (Iterator<PackageStats> j = external.iterator(); j.hasNext();) { String packageName = j.next().getPackageName(); TableTreeItem row = createNewRow(); row.setText(0, "0"); row.setText(1, packageName); row.setText(2, ""); } } /** * @param deps * @param packages */ private void displayInternalPackages(Map<String, Set<String>> deps, List<Set<PackageStats>> packages) { for (int i = packages.size() - 1; i >= 0; i--) { Set<PackageStats> packageSet = packages.get(i); for (Iterator<PackageStats> j = packageSet.iterator(); j.hasNext();) { PackageStats packageStats = j.next(); TableTreeItem row = createNewRow(); if (packageStats.isTangle()) { row.setForeground(getOutOfRangeForeground()); } row.setText(0, "" + i); row.setText(1, packageStats.getPackageName()); String depPackages = ""; if (deps.get(packageStats.getPackageName()) != null) { SortedSet<String> dependencies = new TreeSet<String>(deps.get(packageStats.getPackageName())); for (Object element : dependencies) { String depPackage = (String) element; if (!depPackage.equals(packageStats.getPackageName())) { depPackages += (depPackages.length() != 0 ? ", " : "") + depPackage; } } } row.setText(2, depPackages); } } } private static void calculateLayers(Map<String, Set<String>> deps, Set<PackageStats> external) { layers = new ArrayList<Set<PackageStats>>(); for (Object element : deps.keySet()) { String packageName = (String) element; PackageStats stats = calculatePackageStat(packageName, deps, new ArrayList<String>(), new HashSet<String>(), external); addStat(stats); } } private static void addStat(PackageStats packageStats) { while (layers.size() < packageStats.getLayer() + 1) { layers.add(new TreeSet<PackageStats>()); } layers.get(packageStats.getLayer()).add(packageStats); } static PackageStats calculatePackageStat(String packageName, Map<String, Set<String>> deps, List<String> stack, Set<String> tangleStarts, Set<PackageStats> external) { PackageStats stats = getStat(packageName); // if (stats != null) { // return stats; // } stats = new PackageStats(packageName); Set<String> dependecies = deps.get(packageName); if (dependecies != null) { stack.add(packageName); for (Object element : dependecies) { String dependentPackageName = (String) element; if (!dependentPackageName.equals(packageName)) { if (stack.contains(dependentPackageName)) { // Tangle!! // Mark the start package tangleStarts.add(dependentPackageName); } else { Set<String> childTangles = new HashSet<String>(); PackageStats childStat = calculatePackageStat(dependentPackageName, deps, stack, childTangles, external); if (childTangles.isEmpty()) { stats.raiseTo(childStat.getLayer() + 1); } else { // Child and this node are in the same tangle // Do not add 1 for this child tangleStarts.addAll(childTangles); stats.setTangle(); stats.raiseTo(childStat.getLayer()); if (tangleStarts.contains(packageName)) { // This package is the start of a tangle tangleStarts.remove(packageName); } } } } } stack.remove(stack.size() - 1); } else { external.add(stats); } return stats; } private void setForeground(Metric metric, TableTreeItem row) { if (metric == null) { row.setForeground(getDefaultForeground()); } else { MetricDescriptor md = MetricsPlugin.getDefault().getMetricDescriptor(metric.getName()); Color c = md.isValueInRange(metric.doubleValue()) ? getInRangeForeground() : getOutOfRangeForeground(); row.setForeground(c); } } /** * create a new root row * * @return */ private TableTreeItem createNewRow() { TableTreeItem item = new TableTreeItem(this, SWT.NONE); item.setForeground(getDefaultForeground()); return item; } /** * create a new child row * * @param parent * @return */ private TableTreeItem createNewRow(TableTreeItem parent) { TableTreeItem item = new TableTreeItem(parent, SWT.NONE); item.setForeground(getDefaultForeground()); return item; } /** * Get the default color (for metrics without a max and within range) * * @return */ private Color getDefaultForeground() { RGB color = PreferenceConverter.getColor(MetricsPlugin.getDefault().getPreferenceStore(), "METRICS.defaultColor"); if (lastDefaultColor == null) { lastDefaultColor = new Color(getDisplay(), color); } else if (!lastDefaultColor.getRGB().equals(color)) { lastDefaultColor.dispose(); lastDefaultColor = new Color(getDisplay(), color); } return lastDefaultColor; } /** * Get the in range color (for metrics with a max and within range) * * @return */ private Color getInRangeForeground() { RGB color = PreferenceConverter.getColor(MetricsPlugin.getDefault().getPreferenceStore(), "METRICS.linkedColor"); if (lastInRangeColor == null) { lastInRangeColor = new Color(getDisplay(), color); } else if (!lastInRangeColor.getRGB().equals(color)) { lastInRangeColor.dispose(); lastInRangeColor = new Color(getDisplay(), color); } return lastInRangeColor; } /** * Get the out of range color (for metrics with a max and out of range) * * @return */ private Color getOutOfRangeForeground() { RGB color = PreferenceConverter.getColor(MetricsPlugin.getDefault().getPreferenceStore(), "METRICS.outOfRangeColor"); if (lastOutofRangeColor == null) { lastOutofRangeColor = new Color(getDisplay(), color); } else if (!lastOutofRangeColor.getRGB().equals(color)) { lastOutofRangeColor.dispose(); lastOutofRangeColor = new Color(getDisplay(), color); } return lastOutofRangeColor; } /** * first time simply adds a dummy child if there should be any children. When such a dummy child is already rpesent, replaces it with the real children. This spreads the work load across expansion events instead of constructing the * entire tree up front, which could be very time consuming in large projects. * * @param row * parent TableTreeItem * @param ms * AbstractMetricSource containing the metrics * @param metric * Name of the Metric * @param per * name of the avg/max type */ private void addChildren(TableTreeItem row, AbstractMetricSource ms, String metric, String per) { AbstractMetricSource[] children = ms.getChildrenHaving(per, metric); // don't have to do anything if there are no child metrics if ((children != null) && (children.length > 0)) { if (row.getData("source") != null) { // dummy already present, replace it with the real thing row.setData("source", null); row.getItems()[0].dispose(); sort(children, metric, per); for (AbstractMetricSource element : children) { TableTreeItem child = createNewRow(row); child.setText(getElementName(element.getJavaElement())); Metric val = element.getValue(metric); child.setText(1, (val != null) ? format(val.doubleValue()) : ""); Avg avg = element.getAverage(metric, per); child.setText(2, (avg != null) ? format(avg.doubleValue()) : ""); child.setText(3, (avg != null) ? format(avg.getStandardDeviation()) : ""); Max max = element.getMaximum(metric, per); child.setText(4, (max != null) ? format(max.doubleValue()) : ""); if (max != null) { IJavaElement maxElm = JavaCore.create(max.getHandle()); child.setText(5, getPath(maxElm)); child.setText(6, getMethodName(maxElm)); setForeground(max, child); } else { child.setText(5, ""); child.setText(6, ""); if (val != null) { setForeground(val, row); } } child.setData("handle", element.getHandle()); child.setData("element", element.getJavaElement()); // recurse addChildren(child, element, metric, per); } } else { // add dummy /* TableTreeItem child = */createNewRow(row); row.setData("metric", metric); row.setData("per", per); row.setData("source", ms); } } else { Max max = ms.getMaximum(metric, per); if (max != null) { setForeground(max, row); } else { setForeground(ms.getValue(metric), row); } } } private String getElementName(IJavaElement element) { String candidate = element.getElementName(); if ("".equals(candidate)) { return "(default package)"; } return candidate; } /** * Sort the metrics in descending max/value order, giving preference to max over value (if max exists, use it, otherwise use value) * * @param children * @param metric * @param per * @return */ private void sort(AbstractMetricSource[] children, final String metric, final String per) { Comparator<AbstractMetricSource> c = new Comparator<AbstractMetricSource>() { public int compare(AbstractMetricSource o1, AbstractMetricSource o2) { Max max1 = o1.getMaximum(metric, per); Max max2 = o2.getMaximum(metric, per); if ((max1 != null) && (max2 != null)) { return -max1.compareTo(max2); } /* else { */ Metric m1 = o1.getValue(metric); Metric m2 = o2.getValue(metric); if ((m1 != null) && (m2 != null)) { return -m1.compareTo(m2); } /* else { */ if ((max1 != null) && (max2 == null)) { return -1; } if ((m1 != null) && (m2 == null)) { return -1; // replaced from if ((m2 != null) && (m2 // == null)) return -1; } return 1; /* } */ /* } */ } }; Arrays.sort(children, c); } /* * private void setText(TableTreeItem row, String[] columns) { row.setText(columns[0]); for (int i = 1; i < columns.length; i++) { row.setText(i, columns[i]); } } */ /** * @param handle * @return String */ private String getMethodName(IJavaElement element) { return (element.getElementType() == IJavaElement.METHOD) ? element.getElementName() : ""; } /** * @param handle * @return String */ private String getPath(IJavaElement element) { return element.getPath().toString(); } /** * Method getCompilationUnit. * * @param source * @return Object */ // private ICompilationUnit getCompilationUnit(String path) { // IResource source = ResourcesPlugin.getWorkspace().getRoot().findMember( // path); // if (source.getType() == IResource.FILE) { // return JavaCore.createCompilationUnitFrom((IFile) source); // } // return null; // } private String format(double value) { NumberFormat nf = NumberFormat.getInstance(); int decimals = MetricsPlugin.getDefault().getPreferenceStore().getInt("METRICS.decimals"); nf.setMaximumFractionDigits(decimals); nf.setGroupingUsed(false); return nf.format(value); } /** * @see org.eclipse.swt.widgets.Widget#checkSubclass() */ @Override protected void checkSubclass() { } public void widgetSelected(SelectionEvent e) { } /** * react to double-clicks in the table by opening an editor on the resource for the metric * * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(SelectionEvent) */ public void widgetDefaultSelected(SelectionEvent e) { TableTreeItem row = (TableTreeItem) e.item; IJavaElement element = (IJavaElement) row.getData("element"); String handle = (String) row.getData("handle"); try { if (element != null) { IEditorPart javaEditor = JavaUI.openInEditor(element); if (element instanceof IMember) { JavaUI.revealInEditor(javaEditor, element); } } } catch (PartInitException x) { Log.logError("Error selecting " + handle, x); x.printStackTrace(); } catch (JavaModelException x) { Log.logError("Error selecting " + handle, x); x.printStackTrace(); } catch (Throwable t) { Log.logError("Error selecting " + handle, t); t.printStackTrace(); } } private int getWidth(IMemento m, String name, int defaultVal) { try { Integer val = m.getInteger(name); return (val == null) ? defaultVal : val.intValue(); } catch (Throwable e) { return defaultVal; } } void initWidths(IMemento memento) { layer.setWidth(getWidth(memento, "description", 70)); value.setWidth(getWidth(memento, "value", 250)); dependencies.setWidth(getWidth(memento, "dependencies", 1250)); } void updateWidths(IMemento memento) { memento.putInteger("description", layer.getWidth()); memento.putInteger("value", value.getWidth()); memento.putInteger("dependencies", dependencies.getWidth()); } /* * (non-Javadoc) * * @see org.eclipse.swt.events.TreeListener#treeCollapsed(org.eclipse.swt.events .TreeEvent) */ public void treeCollapsed(TreeEvent e) { } /** * replaces dummy child nodes with the real children if needed */ public void treeExpanded(TreeEvent e) { TableTreeItem item = (TableTreeItem) e.item; if (item.getData("source") != null) { AbstractMetricSource source = (AbstractMetricSource) item.getData("source"); String metric = (String) item.getData("metric"); String per = (String) item.getData("per"); addChildren(item, source, metric, per); } } @Override public void dispose() { super.dispose(); if (lastDefaultColor != null) { lastDefaultColor.dispose(); } if (lastInRangeColor != null) { lastInRangeColor.dispose(); } if (lastOutofRangeColor != null) { lastOutofRangeColor.dispose(); } } public static List<Set<PackageStats>> getLayers() { return layers; } public static int getLayer(String depPackageName) { PackageStats stats = getStat(depPackageName); if (stats != null) { return stats.getLayer(); } if (depPackageName.startsWith("knot")) { return 0; } throw new RuntimeException("Unknown package: \"" + depPackageName + "\""); } private static PackageStats getStat(String packageName) { for (Set<PackageStats> packageSet : layers) { for (Iterator<PackageStats> j = packageSet.iterator(); j.hasNext();) { PackageStats stats = j.next(); if (stats.getPackageName().equals(packageName)) { return stats; } } } for (PackageStats stats : external) { if (stats.getPackageName().equals(packageName)) { return stats; } } return null; } }