hide parent class and interface class references if there are too many of them
[javainspect.git] / src / main / java / eu / svjatoslav / inspector / java / structure / ClassDescriptor.java
old mode 100644 (file)
new mode 100755 (executable)
index 41f92f3..ccae767
@@ -1,10 +1,10 @@
 /*
  * JavaInspect - Utility to visualize java software
- * Copyright (C) 2013, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
- * 
+ * Copyright (C) 2013-2014, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ *
  * This program is free software; you can redistribute it and/or
- * modify it under the terms of version 2 of the GNU General Public License
- * as published by the Free Software Foundation.
+ * modify it under the terms of version 3 of the GNU Lesser General Public License
+ * or later as published by the Free Software Foundation.
  */
 
 package eu.svjatoslav.inspector.java.structure;
@@ -12,9 +12,11 @@ package eu.svjatoslav.inspector.java.structure;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
 
 /**
  * Describes single class instance
@@ -23,11 +25,11 @@ public class ClassDescriptor implements GraphElement {
 
        private static final int MAX_REFERECNES_COUNT = 10;
 
-       public final String fullyQualifiedName;
+       public final String classFullyQualifiedName;
 
-       Map<String, FieldDescriptor> nameToFieldMap = new HashMap<String, FieldDescriptor>();
+       Map<String, FieldDescriptor> nameToFieldMap = new TreeMap<String, FieldDescriptor>();
 
-       public List<MethodDescriptor> methods = new ArrayList<MethodDescriptor>();
+       public SortedSet<MethodDescriptor> methods = new TreeSet<MethodDescriptor>();
 
        /**
         * Incoming arrows will have this color.
@@ -46,7 +48,7 @@ public class ClassDescriptor implements GraphElement {
 
        private boolean isShown = true;
 
-       private final ClassGraph dump;
+       private final ClassGraph classGraph;
 
        List<ClassDescriptor> interfaces = new ArrayList<ClassDescriptor>();
 
@@ -55,20 +57,29 @@ public class ClassDescriptor implements GraphElement {
        /**
         * Amount of field and method references pointing to this class.
         */
-       private int referenceCount = 0;
+       private int referencesCount = 0;
+
+       // for interface, counts amount of found implementations
+       private int implementationsCount = 0;
+
+       // counts amount of times this class is extended
+       private int extensionsCount = 0;
+
+       private ClassDescriptor arrayComponent;
 
        public ClassDescriptor(final Class<? extends Object> clazz,
-                       final ClassGraph dump) {
-               this.dump = dump;
+                       final ClassGraph classGraph) {
+               this.classGraph = classGraph;
 
-               fullyQualifiedName = clazz.getName();
-               dump.nameToClassMap.put(fullyQualifiedName, this);
+               classFullyQualifiedName = clazz.getName();
+
+               classGraph.registerClass(classFullyQualifiedName, this);
 
                isArray = clazz.isArray();
 
                if (isArray) {
                        final Class<?> componentType = clazz.getComponentType();
-                       dump.addClass(componentType);
+                       arrayComponent = classGraph.addClass(componentType);
                }
 
                // System.out.println("class: " + fullyQualifiedName);
@@ -85,14 +96,21 @@ public class ClassDescriptor implements GraphElement {
 
                indexMethods(clazz);
 
-               for (final Class interfaceClass : clazz.getInterfaces())
-                       interfaces.add(dump.addClass(interfaceClass));
+               for (final Class interfaceClass : clazz.getInterfaces()) {
+                       final ClassDescriptor classDescriptor = classGraph
+                                       .addClass(interfaceClass);
+                       classDescriptor.registerImplementation();
+                       interfaces.add(classDescriptor);
+               }
+
+               superClass = classGraph.addClass(clazz.getSuperclass());
+               if (superClass != null)
+                       superClass.registerExtension();
 
-               superClass = dump.addClass(clazz.getSuperclass());
        }
 
        public boolean areReferencesShown() {
-               return referenceCount <= MAX_REFERECNES_COUNT;
+               return referencesCount <= MAX_REFERECNES_COUNT;
        }
 
        public void enlistFieldReferences(final StringBuffer result) {
@@ -125,12 +143,15 @@ public class ClassDescriptor implements GraphElement {
 
                result.append("\n");
                result.append("    // interfaces implemented by class: "
-                               + fullyQualifiedName + "\n");
+                               + classFullyQualifiedName + "\n");
 
                for (final ClassDescriptor interfaceDescriptor : interfaces) {
                        if (!interfaceDescriptor.isVisible())
                                continue;
 
+                       if (!interfaceDescriptor.areReferencesShown())
+                               continue;
+
                        result.append("    " + interfaceDescriptor.getGraphId() + " -> "
                                        + getGraphId() + "[style=\"dotted, tapered\", color=\""
                                        + interfaceDescriptor.getInterfaceColor()
@@ -167,8 +188,12 @@ public class ClassDescriptor implements GraphElement {
                if (!superClass.isVisible())
                        return;
 
+               if (!superClass.areReferencesShown())
+                       return;
+
                result.append("\n");
-               result.append("    // super class for: " + fullyQualifiedName + "\n");
+               result.append("    // super class for: " + classFullyQualifiedName
+                               + "\n");
 
                result.append("    " + superClass.getGraphId() + " -> " + getGraphId()
                                + "[style=\"tapered\", color=\""
@@ -178,7 +203,7 @@ public class ClassDescriptor implements GraphElement {
 
        public void generateDotHeader(final StringBuffer result) {
                result.append("\n");
-               result.append("// Class: " + fullyQualifiedName + "\n");
+               result.append("// Class: " + classFullyQualifiedName + "\n");
 
                result.append("    " + getGraphId() + "[label=<<TABLE "
                                + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
@@ -230,14 +255,21 @@ public class ClassDescriptor implements GraphElement {
 
        public String getClassName(final boolean differentiateArray) {
                // this is needed for nested classes
-               final String actualClassName = fullyQualifiedName.replace('$', '.');
+               final String actualClassName = classFullyQualifiedName
+                               .replace('$', '.');
 
-               final int i = actualClassName.lastIndexOf('.');
-
-               String result = actualClassName.substring(i + 1);
-
-               if (isArray)
-                       result = result.substring(0, result.length() - 1);
+               String result;
+               if (isArray) {
+                       // for arrays use array component instead of array class name
+                       result = arrayComponent.classFullyQualifiedName;
+                       if (result.contains(".")) {
+                               final int i = result.lastIndexOf('.');
+                               result = result.substring(i + 1);
+                       }
+               } else {
+                       final int i = actualClassName.lastIndexOf('.');
+                       result = actualClassName.substring(i + 1);
+               }
 
                if (differentiateArray)
                        if (isArray)
@@ -293,11 +325,24 @@ public class ClassDescriptor implements GraphElement {
                return null;
        }
 
+       /**
+        * Returns field with given name (case is ignored). Or <code>null</code> if
+        * field is not found.
+        */
+       public FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
+
+               for (final String fieldName : nameToFieldMap.keySet())
+                       if (fieldToSearch.equalsIgnoreCase(fieldName))
+                               return nameToFieldMap.get(fieldName);
+
+               return null;
+       }
+
        @Override
        public String getGraphId() {
                final String result = "class_"
-                               + fullyQualifiedName.replace('.', '_').replace(";", "")
-                                               .replace("[L", "").replace('$', '_');
+                               + classFullyQualifiedName.replace('.', '_').replace(";", "")
+                               .replace("[L", "").replace('$', '_');
                return result;
        }
 
@@ -308,25 +353,28 @@ public class ClassDescriptor implements GraphElement {
                return interfaceColor;
        }
 
-       public String getPackageName() {
+       private int getOutgoingReferencesCount() {
+               int result = 0;
 
-               final int i = fullyQualifiedName.lastIndexOf('.');
+               // count method references
+               for (final MethodDescriptor methodDescriptor : methods)
+                       result += methodDescriptor.getOutsideVisibleReferencesCount();
 
-               if (i == -1)
-                       return "";
+               // count field references
+               for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
+                       result += fieldDescriptor.getOutsideVisibleReferencesCount();
 
-               return fullyQualifiedName.substring(0, i).replace("[L", "");
-       }
+               // count implemented interfaces
+               for (final ClassDescriptor classDescriptor : interfaces)
+                       if (classDescriptor.isVisible())
+                               result++;
 
-       public String getParentClassesName() {
-               int i = fullyQualifiedName.lastIndexOf('.');
-               final String fullClassName = fullyQualifiedName.substring(i + 1);
+               // count superclass
+               if (superClass != null)
+                       if (superClass.isVisible())
+                               result++;
 
-               i = fullClassName.lastIndexOf('$');
-               if (i == -1)
-                       return "";
-               final String parentClassesName = fullClassName.substring(0, i);
-               return parentClassesName.replace('$', '.');
+               return result;
        }
 
        // public String getReadableName() {
@@ -343,6 +391,27 @@ public class ClassDescriptor implements GraphElement {
        // return fullyQualifiedName;
        // }
 
+       public String getPackageName() {
+
+               final int i = classFullyQualifiedName.lastIndexOf('.');
+
+               if (i == -1)
+                       return "";
+
+               return classFullyQualifiedName.substring(0, i).replace("[L", "");
+       }
+
+       public String getParentClassesName() {
+               int i = classFullyQualifiedName.lastIndexOf('.');
+               final String fullClassName = classFullyQualifiedName.substring(i + 1);
+
+               i = fullClassName.lastIndexOf('$');
+               if (i == -1)
+                       return "";
+               final String parentClassesName = fullClassName.substring(0, i);
+               return parentClassesName.replace('$', '.');
+       }
+
        public String getSuperClassColor() {
                if (superClassColor == null)
                        superClassColor = Utils.getNextLightColor();
@@ -350,17 +419,45 @@ public class ClassDescriptor implements GraphElement {
                return superClassColor;
        }
 
+       /**
+        * Checks if class has field with given name (case is ignored). Returns
+        * <code>true</code> if such field is found.
+        */
+       public boolean hasFieldIgnoreCase(final String fieldToSearch) {
+
+               for (final String fieldName : nameToFieldMap.keySet())
+                       if (fieldToSearch.equalsIgnoreCase(fieldName))
+                               return true;
+
+               return false;
+       }
+
        public void hide() {
                isShown = false;
        }
 
+       public void hideClassIfNoReferences() {
+               if (!isVisible())
+                       return;
+
+               final int totalReferencesCount = getOutgoingReferencesCount()
+                               + referencesCount + extensionsCount + implementationsCount;
+
+               if (totalReferencesCount == 0) {
+                       hide();
+                       return;
+               }
+
+               return;
+       }
+
        public void indexFields(final Field[] fields) {
                for (final Field field : fields) {
                        if (nameToFieldMap.containsKey(field.getName()))
                                continue;
 
                        final FieldDescriptor fieldDescriptor = new FieldDescriptor(field,
-                                       this, dump);
+                                       this, classGraph);
 
                }
        }
@@ -369,24 +466,47 @@ public class ClassDescriptor implements GraphElement {
                final Method[] methods = clazz.getMethods();
 
                for (final Method method : methods)
-                       new MethodDescriptor(method, this, dump);
+                       new MethodDescriptor(method, this, classGraph);
 
        }
 
        @Override
        public boolean isVisible() {
 
-               if (Utils.isSystemDataType(fullyQualifiedName))
+               if (Utils.isSystemDataType(classFullyQualifiedName))
                        return false;
 
-               if (Utils.isSystemPackage(fullyQualifiedName))
+               if (Utils.isSystemPackage(classFullyQualifiedName))
                        return false;
 
+               if (!classGraph.getFilter().isClassShown(classFullyQualifiedName))
+                       return false;
+
+               if (isArray)
+                       if (arrayComponent != null)
+                               if (Utils
+                                               .isSystemDataType(arrayComponent.classFullyQualifiedName))
+                                       // Do not show references to primitive data types in arrays.
+                                       // That is: there is no point to show reference to byte when
+                                       // we have class with byte array field.
+                                       return false;
+
                return isShown;
        }
 
+       /**
+        * Register event when another class is extending this one.
+        */
+       public void registerExtension() {
+               extensionsCount++;
+       }
+
+       public void registerImplementation() {
+               implementationsCount++;
+       }
+
        public void registerReference() {
-               referenceCount++;
+               referencesCount++;
        }
 
        public void setDistinctiveColor(final String distinctiveColor) {