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 private String fullyQualifiedName;
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 protected void analyzeClass(final Class<? extends Object> clazz) {
76 fullyQualifiedName = 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 protected boolean areReferencesShown() {
115 return referencesCount <= MAX_REFERECNES_COUNT;
118 private 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 private 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 private void enlistImplementedInterfaces(final StringBuffer result) {
143 if (interfaces.isEmpty())
147 result.append(" // interfaces implemented by class: "
148 + fullyQualifiedName + "\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 private 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 private 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 private void enlistSuperClass(final StringBuffer result) {
187 if (superClass == null)
190 if (!superClass.isVisible())
193 if (!superClass.areReferencesShown())
197 result.append(" // super class for: " + fullyQualifiedName + "\n");
199 result.append(" " + superClass.getGraphId() + " -> " + getGraphId()
200 + "[ color=\"" + superClass.getSuperClassColor()
201 + "\", penwidth=10, dir=\"forward\"];\n");
204 private void generateDotHeader(final StringBuffer result) {
206 result.append("// Class: " + fullyQualifiedName + "\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 private String getBackgroundColor() {
231 bgColor = "bgcolor=\"navajowhite2\"";
234 bgColor = "bgcolor=\"darkslategray1\"";
239 private String getBorderWidth() {
241 if (!areReferencesShown())
246 protected ClassGraph getClassGraph() {
250 protected String getClassName(final boolean differentiateArray) {
251 // this is needed for nested classes
252 final String actualClassName = fullyQualifiedName.replace('$', '.');
256 // for arrays use array component instead of array class name
257 result = arrayComponent.fullyQualifiedName;
258 if (result.contains(".")) {
259 final int i = result.lastIndexOf('.');
260 result = result.substring(i + 1);
263 final int i = actualClassName.lastIndexOf('.');
264 result = actualClassName.substring(i + 1);
267 if (differentiateArray)
271 // this is needed for nested classes
272 // result = result.replace('$', '.');
276 protected String getColor() {
277 if (distinctiveReferenceColor == null)
278 distinctiveReferenceColor = Utils.getNextDarkColor();
280 return distinctiveReferenceColor;
284 public String getDot() {
291 final StringBuffer result = new StringBuffer();
293 generateDotHeader(result);
295 enlistFields(result);
297 enlistMethods(result);
299 result.append(" </TABLE>>, shape=\"none\"];\n");
301 enlistFieldReferences(result);
303 enlistMethodReferences(result);
305 enlistImplementedInterfaces(result);
307 enlistSuperClass(result);
309 return result.toString();
313 public String getEmbeddedDot() {
318 * Returns field with given name (case is ignored). Or <code>null</code> if
319 * field is not found.
321 protected FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
323 for (final String fieldName : nameToFieldMap.keySet())
324 if (fieldToSearch.equalsIgnoreCase(fieldName))
325 return nameToFieldMap.get(fieldName);
330 protected String getFullyQualifiedName() {
331 return fullyQualifiedName;
335 public String getGraphId() {
336 final String result = "class_"
337 + fullyQualifiedName.replace('.', '_').replace(";", "")
338 .replace("[L", "").replace('$', '_');
342 private String getInterfaceColor() {
343 if (interfaceColor == null)
344 interfaceColor = Utils.getNextDarkColor();
346 return interfaceColor;
349 private FieldDescriptor getOrCreateFieldDescriptor(final Field field) {
351 final String fieldName = field.getName();
353 if (nameToFieldMap.containsKey(fieldName))
354 return nameToFieldMap.get(fieldName);
356 final FieldDescriptor newFieldDescriptor = new FieldDescriptor(this);
357 nameToFieldMap.put(fieldName, newFieldDescriptor);
359 newFieldDescriptor.analyzeField(field);
361 return newFieldDescriptor;
364 private int getOutgoingReferencesCount() {
367 // count method references
368 for (final MethodDescriptor methodDescriptor : methods)
369 result += methodDescriptor.getOutsideVisibleReferencesCount();
371 // count field references
372 for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
373 result += fieldDescriptor.getOutsideVisibleReferencesCount();
375 // count implemented interfaces
376 for (final ClassDescriptor classDescriptor : interfaces)
377 if (classDescriptor.isVisible())
381 if (superClass != null)
382 if (superClass.isVisible())
388 private String getPackageName() {
390 final int i = fullyQualifiedName.lastIndexOf('.');
395 return fullyQualifiedName.substring(0, i).replace("[L", "");
398 private String getParentClassesName() {
399 int i = fullyQualifiedName.lastIndexOf('.');
400 final String fullClassName = fullyQualifiedName.substring(i + 1);
402 i = fullClassName.lastIndexOf('$');
405 final String parentClassesName = fullClassName.substring(0, i);
406 return parentClassesName.replace('$', '.');
409 private String getSuperClassColor() {
410 if (superClassColor == null)
411 superClassColor = Utils.getNextLightColor();
413 return superClassColor;
417 * Checks if class has field with given name (case is ignored). Returns
418 * <code>true</code> if such field is found.
420 protected boolean hasFieldIgnoreCase(final String fieldToSearch) {
422 for (final String fieldName : nameToFieldMap.keySet())
423 if (fieldToSearch.equalsIgnoreCase(fieldName))
429 private void hide() {
433 protected void hideClassIfNoReferences() {
437 final int totalReferencesCount = getOutgoingReferencesCount()
438 + referencesCount + extensionsCount + implementationsCount;
440 if (totalReferencesCount == 0) {
448 private void indexFields(final Field[] fields) {
449 for (final Field field : fields)
450 getOrCreateFieldDescriptor(field);
453 private void indexMethods(final Class<? extends Object> clazz) {
454 for (final Method method : clazz.getMethods()) {
455 final MethodDescriptor methodDescriptor = new MethodDescriptor(
456 this, method.getName());
458 methods.add(methodDescriptor);
460 methodDescriptor.analyze(method);
466 public boolean isVisible() {
468 if (Utils.isSystemDataType(fullyQualifiedName))
471 if (Utils.isSystemPackage(fullyQualifiedName))
474 if (!getClassGraph().getFilter().isClassShown(fullyQualifiedName))
478 if (arrayComponent != null)
479 if (Utils.isSystemDataType(arrayComponent.fullyQualifiedName))
480 // Do not show references to primitive data types in arrays.
481 // That is: there is no point to show reference to byte when
482 // we have class with byte array field.
489 * Register event when another class is extending this one.
491 protected void registerExtension() {
495 protected void registerImplementation() {
496 implementationsCount++;
499 protected void registerReference() {