2 * JavaInspect - Utility to visualize java software
3 * Copyright (C) 2013-2015, 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 String classFullyQualifiedName;
30 private final Map<String, FieldDescriptor> nameToFieldMap = new TreeMap<String, FieldDescriptor>();
32 private final 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 ClassGraph classGraph) {
71 this.classGraph = classGraph;
74 public void analyzeClass(final Class<? extends Object> clazz) {
76 classFullyQualifiedName = clazz.getName();
78 isArray = clazz.isArray();
81 final Class<?> componentType = clazz.getComponentType();
82 arrayComponent = getClassGraph().getOrCreateClassDescriptor(
86 // System.out.println("class: " + fullyQualifiedName);
88 isEnum = clazz.isEnum();
90 isInterface = clazz.isInterface();
95 indexFields(clazz.getDeclaredFields());
96 indexFields(clazz.getFields());
100 for (final Class interfaceClass : clazz.getInterfaces()) {
101 final ClassDescriptor interfaceClassDescriptor = getClassGraph()
102 .getOrCreateClassDescriptor(interfaceClass);
103 interfaceClassDescriptor.registerImplementation();
104 interfaces.add(interfaceClassDescriptor);
107 superClass = getClassGraph().getOrCreateClassDescriptor(
108 clazz.getSuperclass());
109 if (superClass != null)
110 superClass.registerExtension();
114 public boolean areReferencesShown() {
115 return referencesCount <= MAX_REFERECNES_COUNT;
118 public void enlistFieldReferences(final StringBuffer result) {
119 if (nameToFieldMap.isEmpty())
123 result.append(" // field references to other classes\n");
124 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
126 result.append(entry.getValue().getDot());
129 public void enlistFields(final StringBuffer result) {
130 if (nameToFieldMap.isEmpty())
134 result.append(" // fields:\n");
137 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
139 result.append(entry.getValue().getEmbeddedDot());
142 public void enlistImplementedInterfaces(final StringBuffer result) {
143 if (interfaces.isEmpty())
147 result.append(" // interfaces implemented by class: "
148 + classFullyQualifiedName + "\n");
150 for (final ClassDescriptor interfaceDescriptor : interfaces) {
151 if (!interfaceDescriptor.isVisible())
154 if (!interfaceDescriptor.areReferencesShown())
157 result.append(" " + interfaceDescriptor.getGraphId() + " -> "
158 + getGraphId() + "[style=\"dotted\", color=\""
159 + interfaceDescriptor.getInterfaceColor()
160 + "\", penwidth=10, dir=\"forward\"];\n");
164 public void enlistMethodReferences(final StringBuffer result) {
165 if (methods.isEmpty())
169 result.append(" // method references to other classes\n");
170 for (final MethodDescriptor methodDescriptor : methods)
171 result.append(methodDescriptor.getDot());
174 public void enlistMethods(final StringBuffer result) {
175 if (methods.isEmpty())
179 result.append(" // methods:\n");
182 for (final MethodDescriptor methodDescriptor : methods)
183 result.append(methodDescriptor.getEmbeddedDot());
186 public void enlistSuperClass(final StringBuffer result) {
187 if (superClass == null)
190 if (!superClass.isVisible())
193 if (!superClass.areReferencesShown())
197 result.append(" // super class for: " + classFullyQualifiedName
200 result.append(" " + superClass.getGraphId() + " -> " + getGraphId()
201 + "[ color=\"" + superClass.getSuperClassColor()
202 + "\", penwidth=10, dir=\"forward\"];\n");
205 public void generateDotHeader(final StringBuffer result) {
207 result.append("// Class: " + classFullyQualifiedName + "\n");
209 result.append(" " + getGraphId() + "[label=<<TABLE "
210 + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
211 + "\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
214 result.append(" // class descriptor header\n");
215 result.append(" <TR><TD colspan=\"2\" PORT=\"f0\">"
216 + "<FONT POINT-SIZE=\"8.0\" >" + getPackageName()
219 final String parentClassesName = getParentClassesName();
220 if (parentClassesName.length() > 0)
221 result.append("<FONT POINT-SIZE=\"12.0\"><B>" + parentClassesName
222 + "</B></FONT><br/>\n");
224 result.append("<FONT POINT-SIZE=\"25.0\"><B>" + getClassName(false)
225 + "</B></FONT>" + "</TD></TR>\n");
228 public List<FieldDescriptor> getAllFields() {
229 final List<FieldDescriptor> result = new ArrayList<FieldDescriptor>();
231 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
233 result.add(entry.getValue());
238 public String getBackgroundColor() {
242 bgColor = "bgcolor=\"navajowhite2\"";
245 bgColor = "bgcolor=\"darkslategray1\"";
250 public String getBorderWidth() {
252 if (!areReferencesShown())
257 public ClassGraph getClassGraph() {
261 public String getClassName(final boolean differentiateArray) {
262 // this is needed for nested classes
263 final String actualClassName = classFullyQualifiedName
268 // for arrays use array component instead of array class name
269 result = arrayComponent.classFullyQualifiedName;
270 if (result.contains(".")) {
271 final int i = result.lastIndexOf('.');
272 result = result.substring(i + 1);
275 final int i = actualClassName.lastIndexOf('.');
276 result = actualClassName.substring(i + 1);
279 if (differentiateArray)
283 // this is needed for nested classes
284 // result = result.replace('$', '.');
288 public String getColor() {
289 if (getDistinctiveColor() == null)
290 setDistinctiveColor(Utils.getNextDarkColor());
292 return getDistinctiveColor();
295 public String getDistinctiveColor() {
296 return distinctiveReferenceColor;
300 public String getDot() {
307 final StringBuffer result = new StringBuffer();
309 generateDotHeader(result);
311 enlistFields(result);
313 enlistMethods(result);
315 result.append(" </TABLE>>, shape=\"none\"];\n");
317 enlistFieldReferences(result);
319 enlistMethodReferences(result);
321 enlistImplementedInterfaces(result);
323 enlistSuperClass(result);
325 return result.toString();
329 public String getEmbeddedDot() {
334 * Returns field with given name (case is ignored). Or <code>null</code> if
335 * field is not found.
337 public FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
339 for (final String fieldName : nameToFieldMap.keySet())
340 if (fieldToSearch.equalsIgnoreCase(fieldName))
341 return nameToFieldMap.get(fieldName);
347 public String getGraphId() {
348 final String result = "class_"
349 + classFullyQualifiedName.replace('.', '_').replace(";", "")
350 .replace("[L", "").replace('$', '_');
354 public String getInterfaceColor() {
355 if (interfaceColor == null)
356 interfaceColor = Utils.getNextDarkColor();
358 return interfaceColor;
361 // public String getReadableName() {
363 // // do not print full class name for well known system classes
364 // final String packageName = getPackageName();
366 // if (packageName.equals("java.util"))
367 // return getClassName();
369 // if (packageName.equals("java.lang"))
370 // return getClassName();
372 // return fullyQualifiedName;
375 private FieldDescriptor getOrCreateFieldDescriptor(final Field field) {
377 final String fieldName = field.getName();
379 if (nameToFieldMap.containsKey(fieldName))
380 return nameToFieldMap.get(fieldName);
382 final FieldDescriptor newFieldDescriptor = new FieldDescriptor(this);
383 nameToFieldMap.put(fieldName, newFieldDescriptor);
385 newFieldDescriptor.analyzeField(field);
387 return newFieldDescriptor;
390 private int getOutgoingReferencesCount() {
393 // count method references
394 for (final MethodDescriptor methodDescriptor : methods)
395 result += methodDescriptor.getOutsideVisibleReferencesCount();
397 // count field references
398 for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
399 result += fieldDescriptor.getOutsideVisibleReferencesCount();
401 // count implemented interfaces
402 for (final ClassDescriptor classDescriptor : interfaces)
403 if (classDescriptor.isVisible())
407 if (superClass != null)
408 if (superClass.isVisible())
414 public String getPackageName() {
416 final int i = classFullyQualifiedName.lastIndexOf('.');
421 return classFullyQualifiedName.substring(0, i).replace("[L", "");
424 public String getParentClassesName() {
425 int i = classFullyQualifiedName.lastIndexOf('.');
426 final String fullClassName = classFullyQualifiedName.substring(i + 1);
428 i = fullClassName.lastIndexOf('$');
431 final String parentClassesName = fullClassName.substring(0, i);
432 return parentClassesName.replace('$', '.');
435 public String getSuperClassColor() {
436 if (superClassColor == null)
437 superClassColor = Utils.getNextLightColor();
439 return superClassColor;
443 * Checks if class has field with given name (case is ignored). Returns
444 * <code>true</code> if such field is found.
446 public boolean hasFieldIgnoreCase(final String fieldToSearch) {
448 for (final String fieldName : nameToFieldMap.keySet())
449 if (fieldToSearch.equalsIgnoreCase(fieldName))
459 public void hideClassIfNoReferences() {
463 final int totalReferencesCount = getOutgoingReferencesCount()
464 + referencesCount + extensionsCount + implementationsCount;
466 if (totalReferencesCount == 0) {
474 public void indexFields(final Field[] fields) {
475 for (final Field field : fields)
476 getOrCreateFieldDescriptor(field);
479 private void indexMethods(final Class<? extends Object> clazz) {
480 for (final Method method : clazz.getMethods()) {
481 final MethodDescriptor methodDescriptor = new MethodDescriptor(
482 this, method.getName());
484 methods.add(methodDescriptor);
486 methodDescriptor.analyze(method);
492 public boolean isVisible() {
494 if (Utils.isSystemDataType(classFullyQualifiedName))
497 if (Utils.isSystemPackage(classFullyQualifiedName))
500 if (!getClassGraph().getFilter().isClassShown(classFullyQualifiedName))
504 if (arrayComponent != null)
506 .isSystemDataType(arrayComponent.classFullyQualifiedName))
507 // Do not show references to primitive data types in arrays.
508 // That is: there is no point to show reference to byte when
509 // we have class with byte array field.
516 * Register event when another class is extending this one.
518 public void registerExtension() {
522 public void registerImplementation() {
523 implementationsCount++;
526 public void registerReference() {
530 public void setDistinctiveColor(final String distinctiveColor) {
531 distinctiveReferenceColor = distinctiveColor;