2 * JavaInspect - Utility to visualize java software
3 * Copyright (C) 2013-2014, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of version 3 of the GNU Lesser General Public License
7 * or later as published by the Free Software Foundation.
10 package eu.svjatoslav.inspector.java.structure;
12 import java.lang.reflect.Field;
13 import java.lang.reflect.Method;
14 import java.util.ArrayList;
15 import java.util.List;
17 import java.util.SortedSet;
18 import java.util.TreeMap;
19 import java.util.TreeSet;
22 * Describes single class instance
24 public class ClassDescriptor implements GraphElement {
26 private static final int MAX_REFERECNES_COUNT = 10;
28 public final String classFullyQualifiedName;
30 Map<String, FieldDescriptor> nameToFieldMap = new TreeMap<String, FieldDescriptor>();
32 public SortedSet<MethodDescriptor> methods = new TreeSet<MethodDescriptor>();
35 * Incoming arrows will have this color.
37 private String distinctiveReferenceColor;
39 private String interfaceColor;
41 private String superClassColor;
49 private boolean isShown = true;
51 private final ClassGraph classGraph;
53 List<ClassDescriptor> interfaces = new ArrayList<ClassDescriptor>();
55 ClassDescriptor superClass;
58 * Amount of field and method references pointing to this class.
60 private int referencesCount = 0;
62 // for interface, counts amount of found implementations
63 private int implementationsCount = 0;
65 // counts amount of times this class is extended
66 private int extensionsCount = 0;
68 private ClassDescriptor arrayComponent;
70 public ClassDescriptor(final Class<? extends Object> clazz,
71 final ClassGraph classGraph) {
72 this.classGraph = classGraph;
74 classFullyQualifiedName = clazz.getName();
76 classGraph.registerClass(classFullyQualifiedName, this);
78 isArray = clazz.isArray();
81 final Class<?> componentType = clazz.getComponentType();
82 arrayComponent = classGraph.addClass(componentType);
85 // System.out.println("class: " + fullyQualifiedName);
87 isEnum = clazz.isEnum();
89 isInterface = clazz.isInterface();
94 indexFields(clazz.getDeclaredFields());
95 indexFields(clazz.getFields());
99 for (final Class interfaceClass : clazz.getInterfaces()) {
100 final ClassDescriptor classDescriptor = classGraph
101 .addClass(interfaceClass);
102 classDescriptor.registerImplementation();
103 interfaces.add(classDescriptor);
106 superClass = classGraph.addClass(clazz.getSuperclass());
107 if (superClass != null)
108 superClass.registerExtension();
112 public boolean areReferencesShown() {
113 return referencesCount <= MAX_REFERECNES_COUNT;
116 public void enlistFieldReferences(final StringBuffer result) {
117 if (nameToFieldMap.isEmpty())
121 result.append(" // field references to other classes\n");
122 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
124 result.append(entry.getValue().getDot());
127 public void enlistFields(final StringBuffer result) {
128 if (nameToFieldMap.isEmpty())
132 result.append(" // fields:\n");
135 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
137 result.append(entry.getValue().getEmbeddedDot());
140 public void enlistImplementedInterfaces(final StringBuffer result) {
141 if (interfaces.isEmpty())
145 result.append(" // interfaces implemented by class: "
146 + classFullyQualifiedName + "\n");
148 for (final ClassDescriptor interfaceDescriptor : interfaces) {
149 if (!interfaceDescriptor.isVisible())
152 if (!interfaceDescriptor.areReferencesShown())
155 result.append(" " + interfaceDescriptor.getGraphId() + " -> "
156 + getGraphId() + "[style=\"dotted, tapered\", color=\""
157 + interfaceDescriptor.getInterfaceColor()
158 + "\", penwidth=20, dir=\"forward\"];\n");
162 public void enlistMethodReferences(final StringBuffer result) {
163 if (methods.isEmpty())
167 result.append(" // method references to other classes\n");
168 for (final MethodDescriptor methodDescriptor : methods)
169 result.append(methodDescriptor.getDot());
172 public void enlistMethods(final StringBuffer result) {
173 if (methods.isEmpty())
177 result.append(" // methods:\n");
180 for (final MethodDescriptor methodDescriptor : methods)
181 result.append(methodDescriptor.getEmbeddedDot());
184 public void enlistSuperClass(final StringBuffer result) {
185 if (superClass == null)
188 if (!superClass.isVisible())
191 if (!superClass.areReferencesShown())
195 result.append(" // super class for: " + classFullyQualifiedName
198 result.append(" " + superClass.getGraphId() + " -> " + getGraphId()
199 + "[style=\"tapered\", color=\""
200 + superClass.getSuperClassColor()
201 + "\", penwidth=10, dir=\"forward\"];\n");
204 public void generateDotHeader(final StringBuffer result) {
206 result.append("// Class: " + classFullyQualifiedName + "\n");
208 result.append(" " + getGraphId() + "[label=<<TABLE "
209 + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
210 + "\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
213 result.append(" // class descriptor header\n");
214 result.append(" <TR><TD colspan=\"2\" PORT=\"f0\">"
215 + "<FONT POINT-SIZE=\"8.0\" >" + getPackageName()
218 final String parentClassesName = getParentClassesName();
219 if (parentClassesName.length() > 0)
220 result.append("<FONT POINT-SIZE=\"12.0\"><B>" + parentClassesName
221 + "</B></FONT><br/>\n");
223 result.append("<FONT POINT-SIZE=\"25.0\"><B>" + getClassName(false)
224 + "</B></FONT>" + "</TD></TR>\n");
227 public List<FieldDescriptor> getAllFields() {
228 final List<FieldDescriptor> result = new ArrayList<FieldDescriptor>();
230 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
232 result.add(entry.getValue());
237 public String getBackgroundColor() {
241 bgColor = "bgcolor=\"navajowhite2\"";
244 bgColor = "bgcolor=\"darkslategray1\"";
249 public String getBorderWidth() {
251 if (!areReferencesShown())
256 public String getClassName(final boolean differentiateArray) {
257 // this is needed for nested classes
258 final String actualClassName = classFullyQualifiedName
263 // for arrays use array component instead of array class name
264 result = arrayComponent.classFullyQualifiedName;
265 if (result.contains(".")) {
266 final int i = result.lastIndexOf('.');
267 result = result.substring(i + 1);
270 final int i = actualClassName.lastIndexOf('.');
271 result = actualClassName.substring(i + 1);
274 if (differentiateArray)
278 // this is needed for nested classes
279 // result = result.replace('$', '.');
283 public String getColor() {
284 if (getDistinctiveColor() == null)
285 setDistinctiveColor(Utils.getNextDarkColor());
287 return getDistinctiveColor();
290 public String getDistinctiveColor() {
291 return distinctiveReferenceColor;
295 public String getDot() {
302 final StringBuffer result = new StringBuffer();
304 generateDotHeader(result);
306 enlistFields(result);
308 enlistMethods(result);
310 result.append(" </TABLE>>, shape=\"none\"];\n");
312 enlistFieldReferences(result);
314 enlistMethodReferences(result);
316 enlistImplementedInterfaces(result);
318 enlistSuperClass(result);
320 return result.toString();
324 public String getEmbeddedDot() {
329 * Returns field with given name (case is ignored). Or <code>null</code> if
330 * field is not found.
332 public FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
334 for (final String fieldName : nameToFieldMap.keySet())
335 if (fieldToSearch.equalsIgnoreCase(fieldName))
336 return nameToFieldMap.get(fieldName);
342 public String getGraphId() {
343 final String result = "class_"
344 + classFullyQualifiedName.replace('.', '_').replace(";", "")
345 .replace("[L", "").replace('$', '_');
349 public String getInterfaceColor() {
350 if (interfaceColor == null)
351 interfaceColor = Utils.getNextLightColor();
353 return interfaceColor;
356 private int getOutgoingReferencesCount() {
359 // count method references
360 for (final MethodDescriptor methodDescriptor : methods)
361 result += methodDescriptor.getOutsideVisibleReferencesCount();
363 // count field references
364 for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
365 result += fieldDescriptor.getOutsideVisibleReferencesCount();
367 // count implemented interfaces
368 for (final ClassDescriptor classDescriptor : interfaces)
369 if (classDescriptor.isVisible())
373 if (superClass != null)
374 if (superClass.isVisible())
380 // public String getReadableName() {
382 // // do not print full class name for well known system classes
383 // final String packageName = getPackageName();
385 // if (packageName.equals("java.util"))
386 // return getClassName();
388 // if (packageName.equals("java.lang"))
389 // return getClassName();
391 // return fullyQualifiedName;
394 public String getPackageName() {
396 final int i = classFullyQualifiedName.lastIndexOf('.');
401 return classFullyQualifiedName.substring(0, i).replace("[L", "");
404 public String getParentClassesName() {
405 int i = classFullyQualifiedName.lastIndexOf('.');
406 final String fullClassName = classFullyQualifiedName.substring(i + 1);
408 i = fullClassName.lastIndexOf('$');
411 final String parentClassesName = fullClassName.substring(0, i);
412 return parentClassesName.replace('$', '.');
415 public String getSuperClassColor() {
416 if (superClassColor == null)
417 superClassColor = Utils.getNextLightColor();
419 return superClassColor;
423 * Checks if class has field with given name (case is ignored). Returns
424 * <code>true</code> if such field is found.
426 public boolean hasFieldIgnoreCase(final String fieldToSearch) {
428 for (final String fieldName : nameToFieldMap.keySet())
429 if (fieldToSearch.equalsIgnoreCase(fieldName))
439 public void hideClassIfNoReferences() {
443 final int totalReferencesCount = getOutgoingReferencesCount()
444 + referencesCount + extensionsCount + implementationsCount;
446 if (totalReferencesCount == 0) {
454 public void indexFields(final Field[] fields) {
455 for (final Field field : fields) {
456 if (nameToFieldMap.containsKey(field.getName()))
459 final FieldDescriptor fieldDescriptor = new FieldDescriptor(field,
465 private void indexMethods(final Class<? extends Object> clazz) {
466 final Method[] methods = clazz.getMethods();
468 for (final Method method : methods)
469 new MethodDescriptor(method, this, classGraph);
474 public boolean isVisible() {
476 if (Utils.isSystemDataType(classFullyQualifiedName))
479 if (Utils.isSystemPackage(classFullyQualifiedName))
482 if (!classGraph.getFilter().isClassShown(classFullyQualifiedName))
486 if (arrayComponent != null)
488 .isSystemDataType(arrayComponent.classFullyQualifiedName))
489 // Do not show references to primitive data types in arrays.
490 // That is: there is no point to show reference to byte when
491 // we have class with byte array field.
498 * Register event when another class is extending this one.
500 public void registerExtension() {
504 public void registerImplementation() {
505 implementationsCount++;
508 public void registerReference() {
512 public void setDistinctiveColor(final String distinctiveColor) {
513 distinctiveReferenceColor = distinctiveColor;