X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Feu%2Fsvjatoslav%2Finspector%2Fjava%2Fstructure%2FClassDescriptor.java;h=118994e92489476ee2233c4f0a5ef05f659a8665;hb=7d1259aea992843c47f29c932434a88ea9364f7e;hp=e43fe8d3c3d04a630d839bd4b857d98a5763fb8b;hpb=934aea34806546c732112258e5e98a482cf9d50e;p=javainspect.git diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/ClassDescriptor.java b/src/main/java/eu/svjatoslav/inspector/java/structure/ClassDescriptor.java index e43fe8d..118994e 100755 --- a/src/main/java/eu/svjatoslav/inspector/java/structure/ClassDescriptor.java +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/ClassDescriptor.java @@ -1,6 +1,6 @@ /* * JavaInspect - Utility to visualize java software - * Copyright (C) 2013-2015, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * Copyright (C) 2013-2020, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu * * This program is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public License @@ -11,493 +11,508 @@ package eu.svjatoslav.inspector.java.structure; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; /** * Describes single class instance */ -public class ClassDescriptor implements GraphElement { +public class ClassDescriptor implements GraphElement, Comparable { + + private static final int MAX_REFERECNES_COUNT = 10; + final List interfaces = new ArrayList<>(); + private final Map nameToFieldMap = new TreeMap<>(); + private final SortedSet methods = new TreeSet<>(); + private final ClassGraph classGraph; + boolean isEnum; + boolean isInterface; + boolean isArray; + ClassDescriptor superClass; + private String fullyQualifiedName; + /** + * Incoming arrows will have this color. + */ + private String distinctiveReferenceColor; + private String interfaceColor; + private String superClassColor; + private boolean isShown = true; + /** + * Amount of field and method references pointing to this class. + */ + 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 ClassGraph classGraph) { + this.classGraph = classGraph; + } + + protected void analyzeClass(final Class clazz) { + + fullyQualifiedName = clazz.getName(); + + isArray = clazz.isArray(); + + if (isArray) { + final Class componentType = clazz.getComponentType(); + arrayComponent = getClassGraph().getOrCreateClassDescriptor( + componentType); + } + + // System.out.println("class: " + fullyQualifiedName); + + isEnum = clazz.isEnum(); + + isInterface = clazz.isInterface(); + + if (!isVisible()) + return; + + try { + indexFields(clazz.getDeclaredFields()); + indexFields(clazz.getFields()); + } catch (NoClassDefFoundError error) { + // TODO: better logging of this error + System.out.println(error.toString()); + } + + indexMethods(clazz); + + for (final Class interfaceClass : clazz.getInterfaces()) { + final ClassDescriptor interfaceClassDescriptor = getClassGraph() + .getOrCreateClassDescriptor(interfaceClass); + interfaceClassDescriptor.registerImplementation(); + interfaces.add(interfaceClassDescriptor); + } + + superClass = getClassGraph().getOrCreateClassDescriptor( + clazz.getSuperclass()); + if (superClass != null) + superClass.registerExtension(); + + } + + protected boolean areReferencesShown() { + return referencesCount <= MAX_REFERECNES_COUNT; + } + + private void enlistFieldReferences(final StringBuffer result) { + if (nameToFieldMap.isEmpty()) + return; + + result.append("\n"); + result.append(" // field references to other classes\n"); + nameToFieldMap.forEach((fieldName, field) -> result.append(field.getDot())); + } + + private void enlistFields(final StringBuffer result) { + if (nameToFieldMap.isEmpty()) + return; + + result.append("\n"); + result.append(" // fields:\n"); + + // enlist fields + for (final Map.Entry entry : nameToFieldMap + .entrySet()) + result.append(entry.getValue().getEmbeddedDot()); + } + + private void enlistImplementedInterfaces(final StringBuffer result) { + if (interfaces.isEmpty()) + return; - private static final int MAX_REFERECNES_COUNT = 10; + result.append("\n"); + result.append(" // interfaces implemented by class: " + + fullyQualifiedName + "\n"); - private String fullyQualifiedName; + for (final ClassDescriptor interfaceDescriptor : interfaces) { + if (!interfaceDescriptor.isVisible()) + continue; - private final Map nameToFieldMap = new TreeMap(); + if (!interfaceDescriptor.areReferencesShown()) + continue; - private final SortedSet methods = new TreeSet(); + result.append(" " + interfaceDescriptor.getGraphId() + " -> " + + getGraphId() + "[style=\"dotted\", color=\"" + + interfaceDescriptor.getInterfaceColor() + + "\", penwidth=10, dir=\"forward\"];\n"); + } + } - /** - * Incoming arrows will have this color. - */ - private String distinctiveReferenceColor; + private void enlistMethodReferences(final StringBuffer result) { + if (methods.isEmpty()) + return; + + result.append("\n"); + result.append(" // method references to other classes\n"); + for (final MethodDescriptor methodDescriptor : methods) + result.append(methodDescriptor.getDot()); + } + + private void enlistMethods(final StringBuffer result) { + if (methods.isEmpty()) + return; + + result.append("\n"); + result.append(" // methods:\n"); + + // enlist methods + for (final MethodDescriptor methodDescriptor : methods) + result.append(methodDescriptor.getEmbeddedDot()); + } + + private void enlistSuperClass(final StringBuffer result) { + if (superClass == null) + return; + + if (!superClass.isVisible()) + return; + + if (!superClass.areReferencesShown()) + return; + + result.append("\n"); + result.append(" // super class for: " + fullyQualifiedName + "\n"); - private String interfaceColor; + result.append(" " + superClass.getGraphId() + " -> " + getGraphId() + + "[ color=\"" + superClass.getSuperClassColor() + + "\", penwidth=10, dir=\"forward\"];\n"); + } - private String superClassColor; + private void generateDotHeader(final StringBuffer result) { + result.append("\n"); + result.append("// Class: " + fullyQualifiedName + "\n"); - boolean isEnum; + result.append(" " + getGraphId() + "[label=<\n"); - boolean isInterface; + result.append("\n"); + result.append(" // class descriptor header\n"); + result.append(" \n"); + } - private final ClassGraph classGraph; + private String getBackgroundColor() { + String bgColor = ""; - List interfaces = new ArrayList(); + if (isEnum) + bgColor = "bgcolor=\"navajowhite2\""; - ClassDescriptor superClass; + if (isInterface) + bgColor = "bgcolor=\"darkslategray1\""; - /** - * Amount of field and method references pointing to this class. - */ - private int referencesCount = 0; + return bgColor; + } - // for interface, counts amount of found implementations - private int implementationsCount = 0; + private String getBorderWidth() { - // counts amount of times this class is extended - private int extensionsCount = 0; + if (!areReferencesShown()) + return "4"; + return "1"; + } - private ClassDescriptor arrayComponent; + protected ClassGraph getClassGraph() { + return classGraph; + } - public ClassDescriptor(final ClassGraph classGraph) { - this.classGraph = classGraph; - } + protected String getClassName(final boolean differentiateArray) { + // this is needed for nested classes + final String actualClassName = fullyQualifiedName.replace('$', '.'); - protected void analyzeClass(final Class clazz) { + String result; + if (isArray) { + // for arrays use array component instead of array class name + result = arrayComponent.fullyQualifiedName; + if (result.contains(".")) { + final int i = result.lastIndexOf('.'); + result = result.substring(i + 1); + } + } else { + final int i = actualClassName.lastIndexOf('.'); + result = actualClassName.substring(i + 1); + } - fullyQualifiedName = clazz.getName(); + if (differentiateArray) + if (isArray) + result += " []"; - isArray = clazz.isArray(); + // this is needed for nested classes + // result = result.replace('$', '.'); + return result; + } - if (isArray) { - final Class componentType = clazz.getComponentType(); - arrayComponent = getClassGraph().getOrCreateClassDescriptor( - componentType); - } + protected String getColor() { + if (distinctiveReferenceColor == null) + distinctiveReferenceColor = Utils.getNextDarkColor(); - // System.out.println("class: " + fullyQualifiedName); + return distinctiveReferenceColor; + } - isEnum = clazz.isEnum(); + @Override + public String getDot() { + if (!isVisible()) + return ""; - isInterface = clazz.isInterface(); + if (isArray) + return ""; - if (!isVisible()) - return; + final StringBuffer result = new StringBuffer(); - indexFields(clazz.getDeclaredFields()); - indexFields(clazz.getFields()); + generateDotHeader(result); - indexMethods(clazz); + enlistFields(result); - for (final Class interfaceClass : clazz.getInterfaces()) { - final ClassDescriptor interfaceClassDescriptor = getClassGraph() - .getOrCreateClassDescriptor(interfaceClass); - interfaceClassDescriptor.registerImplementation(); - interfaces.add(interfaceClassDescriptor); - } + enlistMethods(result); - superClass = getClassGraph().getOrCreateClassDescriptor( - clazz.getSuperclass()); - if (superClass != null) - superClass.registerExtension(); + result.append("
" + + "" + getPackageName() + + "
"); - boolean isArray; + final String parentClassesName = getParentClassesName(); + if (parentClassesName.length() > 0) + result.append("" + parentClassesName + + "
\n"); - private boolean isShown = true; + result.append("" + getClassName(false) + + "" + "
>, shape=\"none\"];\n"); - }; + enlistFieldReferences(result); - protected boolean areReferencesShown() { - return referencesCount <= MAX_REFERECNES_COUNT; - } + enlistMethodReferences(result); - private void enlistFieldReferences(final StringBuffer result) { - if (nameToFieldMap.isEmpty()) - return; + enlistImplementedInterfaces(result); - result.append("\n"); - result.append(" // field references to other classes\n"); - for (final Map.Entry entry : nameToFieldMap - .entrySet()) - result.append(entry.getValue().getDot()); - } + enlistSuperClass(result); - private void enlistFields(final StringBuffer result) { - if (nameToFieldMap.isEmpty()) - return; + return result.toString(); + } - result.append("\n"); - result.append(" // fields:\n"); + @Override + public String getEmbeddedDot() { + return null; + } - // enlist fields - for (final Map.Entry entry : nameToFieldMap - .entrySet()) - result.append(entry.getValue().getEmbeddedDot()); - } + /** + * Returns field with given name (case is ignored). Or null if + * field is not found. + * + * @param fieldToSearch field name (case is ignored) + * @return field matching given name + */ + protected FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) { - private void enlistImplementedInterfaces(final StringBuffer result) { - if (interfaces.isEmpty()) - return; + for (final String fieldName : nameToFieldMap.keySet()) + if (fieldToSearch.equalsIgnoreCase(fieldName)) + return nameToFieldMap.get(fieldName); - result.append("\n"); - result.append(" // interfaces implemented by class: " - + fullyQualifiedName + "\n"); + return null; + } - for (final ClassDescriptor interfaceDescriptor : interfaces) { - if (!interfaceDescriptor.isVisible()) - continue; + protected String getFullyQualifiedName() { + return fullyQualifiedName; + } - if (!interfaceDescriptor.areReferencesShown()) - continue; + @Override + public String getGraphId() { - result.append(" " + interfaceDescriptor.getGraphId() + " -> " - + getGraphId() + "[style=\"dotted\", color=\"" - + interfaceDescriptor.getInterfaceColor() - + "\", penwidth=10, dir=\"forward\"];\n"); - } - } + return "class_" + + fullyQualifiedName + .replace('.', '_') + .replace(";", "") + .replace("[[", "") + .replace("[L", "") + .replace("[[L", "") // array of arrays + .replace("[[[L", "") // array of arrays of arrays + .replace('$', '_'); + } - private void enlistMethodReferences(final StringBuffer result) { - if (methods.isEmpty()) - return; + private String getInterfaceColor() { + if (interfaceColor == null) + interfaceColor = Utils.getNextDarkColor(); - result.append("\n"); - result.append(" // method references to other classes\n"); - for (final MethodDescriptor methodDescriptor : methods) - result.append(methodDescriptor.getDot()); - } + return interfaceColor; + } - private void enlistMethods(final StringBuffer result) { - if (methods.isEmpty()) - return; - - result.append("\n"); - result.append(" // methods:\n"); - - // enlist methods - for (final MethodDescriptor methodDescriptor : methods) - result.append(methodDescriptor.getEmbeddedDot()); - } - - private void enlistSuperClass(final StringBuffer result) { - if (superClass == null) - return; - - if (!superClass.isVisible()) - return; - - if (!superClass.areReferencesShown()) - return; - - result.append("\n"); - result.append(" // super class for: " + fullyQualifiedName + "\n"); - - result.append(" " + superClass.getGraphId() + " -> " + getGraphId() - + "[ color=\"" + superClass.getSuperClassColor() - + "\", penwidth=10, dir=\"forward\"];\n"); - } + private FieldDescriptor getOrCreateFieldDescriptor(final Field field) { - private void generateDotHeader(final StringBuffer result) { - result.append("\n"); - result.append("// Class: " + fullyQualifiedName + "\n"); + final String fieldName = field.getName(); - result.append(" " + getGraphId() + "[label=<\n"); + if (nameToFieldMap.containsKey(fieldName)) + return nameToFieldMap.get(fieldName); - result.append("\n"); - result.append(" // class descriptor header\n"); - result.append(" \n"); - } + return newFieldDescriptor; + } - private String getBackgroundColor() { - String bgColor = ""; + private int getOutgoingReferencesCount() { + int result = 0; - if (isEnum) - bgColor = "bgcolor=\"navajowhite2\""; + // count method references + for (final MethodDescriptor methodDescriptor : methods) + result += methodDescriptor.getOutsideVisibleReferencesCount(); - if (isInterface) - bgColor = "bgcolor=\"darkslategray1\""; + // count field references + for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values()) + result += fieldDescriptor.getOutsideVisibleReferencesCount(); - return bgColor; - } + // count implemented interfaces + for (final ClassDescriptor classDescriptor : interfaces) + if (classDescriptor.isVisible()) + result++; - private String getBorderWidth() { + // count superclass + if (superClass != null) + if (superClass.isVisible()) + result++; - if (!areReferencesShown()) - return "4"; - return "1"; - } + return result; + } - protected ClassGraph getClassGraph() { - return classGraph; - } + private String getPackageName() { - protected String getClassName(final boolean differentiateArray) { - // this is needed for nested classes - final String actualClassName = fullyQualifiedName.replace('$', '.'); + final int i = fullyQualifiedName.lastIndexOf('.'); - String result; - if (isArray) { - // for arrays use array component instead of array class name - result = arrayComponent.fullyQualifiedName; - 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 (i == -1) + return ""; - if (differentiateArray) - if (isArray) - result += " []"; + return fullyQualifiedName.substring(0, i).replace("[L", ""); + } - // this is needed for nested classes - // result = result.replace('$', '.'); - return result; - } + private String getParentClassesName() { + int i = fullyQualifiedName.lastIndexOf('.'); + final String fullClassName = fullyQualifiedName.substring(i + 1); - protected String getColor() { - if (distinctiveReferenceColor == null) - distinctiveReferenceColor = Utils.getNextDarkColor(); + i = fullClassName.lastIndexOf('$'); + if (i == -1) + return ""; + final String parentClassesName = fullClassName.substring(0, i); + return parentClassesName.replace('$', '.'); + } - return distinctiveReferenceColor; - } + private String getSuperClassColor() { + if (superClassColor == null) + superClassColor = Utils.getNextLightColor(); - @Override - public String getDot() { - if (!isVisible()) - return ""; + return superClassColor; + } - if (isArray) - return ""; + /** + * Checks if class has field with given name (case is ignored). Returns + * true if such field is found. + * + * @param fieldToSearch field to search for (case is ignored) + * @return true if field is found. + */ + protected boolean hasFieldIgnoreCase(final String fieldToSearch) { - final StringBuffer result = new StringBuffer(); + for (final String fieldName : nameToFieldMap.keySet()) + if (fieldToSearch.equalsIgnoreCase(fieldName)) + return true; - generateDotHeader(result); + return false; + } - enlistFields(result); + private void hide() { + isShown = false; + } - enlistMethods(result); + protected void hideClassIfNoReferences() { + if (!isVisible()) + return; - result.append("
" - + "" + getPackageName() - + "
"); + final FieldDescriptor newFieldDescriptor = new FieldDescriptor(this); + nameToFieldMap.put(fieldName, newFieldDescriptor); - final String parentClassesName = getParentClassesName(); - if (parentClassesName.length() > 0) - result.append("" + parentClassesName - + "
\n"); + newFieldDescriptor.analyzeField(field); - result.append("" + getClassName(false) - + "" + "
>, shape=\"none\"];\n"); + final int totalReferencesCount = getOutgoingReferencesCount() + + referencesCount + extensionsCount + implementationsCount; - enlistFieldReferences(result); + if (totalReferencesCount == 0) { + hide(); + return; + } - enlistMethodReferences(result); + } - enlistImplementedInterfaces(result); + private void indexFields(final Field[] fields) { + for (final Field field : fields) + getOrCreateFieldDescriptor(field); + } - enlistSuperClass(result); + private void indexMethods(final Class clazz) { + for (final Method method : clazz.getMethods()) { + final MethodDescriptor methodDescriptor = new MethodDescriptor( + this, method.getName()); - return result.toString(); - } + methods.add(methodDescriptor); - @Override - public String getEmbeddedDot() { - return null; - } + methodDescriptor.analyze(method); + } - /** - * Returns field with given name (case is ignored). Or null if - * field is not found. - */ - protected FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) { + } - for (final String fieldName : nameToFieldMap.keySet()) - if (fieldToSearch.equalsIgnoreCase(fieldName)) - return nameToFieldMap.get(fieldName); + @Override + public boolean isVisible() { - return null; - } + if (Utils.isSystemDataType(fullyQualifiedName)) + return false; - protected String getFullyQualifiedName() { - return fullyQualifiedName; - } + if (Utils.isSystemPackage(fullyQualifiedName)) + return false; - @Override - public String getGraphId() { - final String result = "class_" - + fullyQualifiedName.replace('.', '_').replace(";", "") - .replace("[L", "").replace('$', '_'); - return result; - } + if (!getClassGraph().isClassShown(fullyQualifiedName)) + return false; - private String getInterfaceColor() { - if (interfaceColor == null) - interfaceColor = Utils.getNextDarkColor(); + if (isArray) + if (arrayComponent != null) + if (Utils.isSystemDataType(arrayComponent.fullyQualifiedName)) + // 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 interfaceColor; - } + return isShown; + } - private FieldDescriptor getOrCreateFieldDescriptor(final Field field) { + /** + * Register event when another class is extending this one. + */ + protected void registerExtension() { + extensionsCount++; + } - final String fieldName = field.getName(); + protected void registerImplementation() { + implementationsCount++; + } - if (nameToFieldMap.containsKey(fieldName)) - return nameToFieldMap.get(fieldName); + protected void registerReference() { + referencesCount++; + } - final FieldDescriptor newFieldDescriptor = new FieldDescriptor(this); - nameToFieldMap.put(fieldName, newFieldDescriptor); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ClassDescriptor)) return false; - newFieldDescriptor.analyzeField(field); + ClassDescriptor that = (ClassDescriptor) o; - return newFieldDescriptor; - } + return getFullyQualifiedName().equals(that.getFullyQualifiedName()); - private int getOutgoingReferencesCount() { - int result = 0; + } - // count method references - for (final MethodDescriptor methodDescriptor : methods) - result += methodDescriptor.getOutsideVisibleReferencesCount(); - - // count field references - for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values()) - result += fieldDescriptor.getOutsideVisibleReferencesCount(); - - // count implemented interfaces - for (final ClassDescriptor classDescriptor : interfaces) - if (classDescriptor.isVisible()) - result++; - - // count superclass - if (superClass != null) - if (superClass.isVisible()) - result++; - - return result; - } - - private String getPackageName() { - - final int i = fullyQualifiedName.lastIndexOf('.'); - - if (i == -1) - return ""; - - return fullyQualifiedName.substring(0, i).replace("[L", ""); - } - - private String getParentClassesName() { - int i = fullyQualifiedName.lastIndexOf('.'); - final String fullClassName = fullyQualifiedName.substring(i + 1); - - i = fullClassName.lastIndexOf('$'); - if (i == -1) - return ""; - final String parentClassesName = fullClassName.substring(0, i); - return parentClassesName.replace('$', '.'); - } - - private String getSuperClassColor() { - if (superClassColor == null) - superClassColor = Utils.getNextLightColor(); - - return superClassColor; - } - - /** - * Checks if class has field with given name (case is ignored). Returns - * true if such field is found. - */ - protected boolean hasFieldIgnoreCase(final String fieldToSearch) { - - for (final String fieldName : nameToFieldMap.keySet()) - if (fieldToSearch.equalsIgnoreCase(fieldName)) - return true; - - return false; - } - - private void hide() { - isShown = false; - } - - protected void hideClassIfNoReferences() { - if (!isVisible()) - return; - - final int totalReferencesCount = getOutgoingReferencesCount() - + referencesCount + extensionsCount + implementationsCount; - - if (totalReferencesCount == 0) { - hide(); - return; - } - - return; - } - - private void indexFields(final Field[] fields) { - for (final Field field : fields) - getOrCreateFieldDescriptor(field); - } - - private void indexMethods(final Class clazz) { - for (final Method method : clazz.getMethods()) { - final MethodDescriptor methodDescriptor = new MethodDescriptor( - this, method.getName()); - - methods.add(methodDescriptor); - - methodDescriptor.analyze(method); - } - - } - - @Override - public boolean isVisible() { - - if (Utils.isSystemDataType(fullyQualifiedName)) - return false; - - if (Utils.isSystemPackage(fullyQualifiedName)) - return false; - - if (!getClassGraph().getFilter().isClassShown(fullyQualifiedName)) - return false; - - if (isArray) - if (arrayComponent != null) - if (Utils.isSystemDataType(arrayComponent.fullyQualifiedName)) - // 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. - */ - protected void registerExtension() { - extensionsCount++; - } - - protected void registerImplementation() { - implementationsCount++; - } - - protected void registerReference() { - referencesCount++; - } + @Override + public int hashCode() { + return getFullyQualifiedName().hashCode(); + } + @Override + public int compareTo(ClassDescriptor o) { + return fullyQualifiedName.compareTo(o.fullyQualifiedName); + } }