2 * JavaInspect - Utility to visualize java software
3 * Copyright (C) 2013-2017, 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;
17 * Describes single class instance
19 public class ClassDescriptor implements GraphElement, Comparable<ClassDescriptor> {
21 private static final int MAX_REFERECNES_COUNT = 10;
22 private final Map<String, FieldDescriptor> nameToFieldMap = new TreeMap<String, FieldDescriptor>();
23 private final SortedSet<MethodDescriptor> methods = new TreeSet<MethodDescriptor>();
24 private final ClassGraph classGraph;
28 List<ClassDescriptor> interfaces = new ArrayList<ClassDescriptor>();
29 ClassDescriptor superClass;
30 private String fullyQualifiedName;
32 * Incoming arrows will have this color.
34 private String distinctiveReferenceColor;
35 private String interfaceColor;
36 private String superClassColor;
37 private boolean isShown = true;
39 * Amount of field and method references pointing to this class.
41 private int referencesCount = 0;
43 // for interface, counts amount of found implementations
44 private int implementationsCount = 0;
46 // counts amount of times this class is extended
47 private int extensionsCount = 0;
49 private ClassDescriptor arrayComponent;
51 public ClassDescriptor(final ClassGraph classGraph) {
52 this.classGraph = classGraph;
55 protected void analyzeClass(final Class<? extends Object> clazz) {
57 fullyQualifiedName = clazz.getName();
59 isArray = clazz.isArray();
62 final Class<?> componentType = clazz.getComponentType();
63 arrayComponent = getClassGraph().getOrCreateClassDescriptor(
67 // System.out.println("class: " + fullyQualifiedName);
69 isEnum = clazz.isEnum();
71 isInterface = clazz.isInterface();
77 indexFields(clazz.getDeclaredFields());
78 indexFields(clazz.getFields());
79 } catch (NoClassDefFoundError error){
80 // TODO: better logging of this error
81 System.out.println(error.toString());
86 for (final Class interfaceClass : clazz.getInterfaces()) {
87 final ClassDescriptor interfaceClassDescriptor = getClassGraph()
88 .getOrCreateClassDescriptor(interfaceClass);
89 interfaceClassDescriptor.registerImplementation();
90 interfaces.add(interfaceClassDescriptor);
93 superClass = getClassGraph().getOrCreateClassDescriptor(
94 clazz.getSuperclass());
95 if (superClass != null)
96 superClass.registerExtension();
100 protected boolean areReferencesShown() {
101 return referencesCount <= MAX_REFERECNES_COUNT;
104 private void enlistFieldReferences(final StringBuffer result) {
105 if (nameToFieldMap.isEmpty())
109 result.append(" // field references to other classes\n");
110 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
112 result.append(entry.getValue().getDot());
115 private void enlistFields(final StringBuffer result) {
116 if (nameToFieldMap.isEmpty())
120 result.append(" // fields:\n");
123 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
125 result.append(entry.getValue().getEmbeddedDot());
128 private void enlistImplementedInterfaces(final StringBuffer result) {
129 if (interfaces.isEmpty())
133 result.append(" // interfaces implemented by class: "
134 + fullyQualifiedName + "\n");
136 for (final ClassDescriptor interfaceDescriptor : interfaces) {
137 if (!interfaceDescriptor.isVisible())
140 if (!interfaceDescriptor.areReferencesShown())
143 result.append(" " + interfaceDescriptor.getGraphId() + " -> "
144 + getGraphId() + "[style=\"dotted\", color=\""
145 + interfaceDescriptor.getInterfaceColor()
146 + "\", penwidth=10, dir=\"forward\"];\n");
150 private void enlistMethodReferences(final StringBuffer result) {
151 if (methods.isEmpty())
155 result.append(" // method references to other classes\n");
156 for (final MethodDescriptor methodDescriptor : methods)
157 result.append(methodDescriptor.getDot());
160 private void enlistMethods(final StringBuffer result) {
161 if (methods.isEmpty())
165 result.append(" // methods:\n");
168 for (final MethodDescriptor methodDescriptor : methods)
169 result.append(methodDescriptor.getEmbeddedDot());
172 private void enlistSuperClass(final StringBuffer result) {
173 if (superClass == null)
176 if (!superClass.isVisible())
179 if (!superClass.areReferencesShown())
183 result.append(" // super class for: " + fullyQualifiedName + "\n");
185 result.append(" " + superClass.getGraphId() + " -> " + getGraphId()
186 + "[ color=\"" + superClass.getSuperClassColor()
187 + "\", penwidth=10, dir=\"forward\"];\n");
190 private void generateDotHeader(final StringBuffer result) {
192 result.append("// Class: " + fullyQualifiedName + "\n");
194 result.append(" " + getGraphId() + "[label=<<TABLE "
195 + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
196 + "\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
199 result.append(" // class descriptor header\n");
200 result.append(" <TR><TD colspan=\"2\" PORT=\"f0\">"
201 + "<FONT POINT-SIZE=\"8.0\" >" + getPackageName()
204 final String parentClassesName = getParentClassesName();
205 if (parentClassesName.length() > 0)
206 result.append("<FONT POINT-SIZE=\"12.0\"><B>" + parentClassesName
207 + "</B></FONT><br/>\n");
209 result.append("<FONT POINT-SIZE=\"25.0\"><B>" + getClassName(false)
210 + "</B></FONT>" + "</TD></TR>\n");
213 private String getBackgroundColor() {
217 bgColor = "bgcolor=\"navajowhite2\"";
220 bgColor = "bgcolor=\"darkslategray1\"";
225 private String getBorderWidth() {
227 if (!areReferencesShown())
232 protected ClassGraph getClassGraph() {
236 protected String getClassName(final boolean differentiateArray) {
237 // this is needed for nested classes
238 final String actualClassName = fullyQualifiedName.replace('$', '.');
242 // for arrays use array component instead of array class name
243 result = arrayComponent.fullyQualifiedName;
244 if (result.contains(".")) {
245 final int i = result.lastIndexOf('.');
246 result = result.substring(i + 1);
249 final int i = actualClassName.lastIndexOf('.');
250 result = actualClassName.substring(i + 1);
253 if (differentiateArray)
257 // this is needed for nested classes
258 // result = result.replace('$', '.');
262 protected String getColor() {
263 if (distinctiveReferenceColor == null)
264 distinctiveReferenceColor = Utils.getNextDarkColor();
266 return distinctiveReferenceColor;
270 public String getDot() {
277 final StringBuffer result = new StringBuffer();
279 generateDotHeader(result);
281 enlistFields(result);
283 enlistMethods(result);
285 result.append(" </TABLE>>, shape=\"none\"];\n");
287 enlistFieldReferences(result);
289 enlistMethodReferences(result);
291 enlistImplementedInterfaces(result);
293 enlistSuperClass(result);
295 return result.toString();
299 public String getEmbeddedDot() {
304 * Returns field with given name (case is ignored). Or <code>null</code> if
305 * field is not found.
307 * @param fieldToSearch field name (case is ignored)
308 * @return field matching given name
310 protected FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
312 for (final String fieldName : nameToFieldMap.keySet())
313 if (fieldToSearch.equalsIgnoreCase(fieldName))
314 return nameToFieldMap.get(fieldName);
319 protected String getFullyQualifiedName() {
320 return fullyQualifiedName;
324 public String getGraphId() {
325 final String result = "class_"
335 private String getInterfaceColor() {
336 if (interfaceColor == null)
337 interfaceColor = Utils.getNextDarkColor();
339 return interfaceColor;
342 private FieldDescriptor getOrCreateFieldDescriptor(final Field field) {
344 final String fieldName = field.getName();
346 if (nameToFieldMap.containsKey(fieldName))
347 return nameToFieldMap.get(fieldName);
349 final FieldDescriptor newFieldDescriptor = new FieldDescriptor(this);
350 nameToFieldMap.put(fieldName, newFieldDescriptor);
352 newFieldDescriptor.analyzeField(field);
354 return newFieldDescriptor;
357 private int getOutgoingReferencesCount() {
360 // count method references
361 for (final MethodDescriptor methodDescriptor : methods)
362 result += methodDescriptor.getOutsideVisibleReferencesCount();
364 // count field references
365 for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
366 result += fieldDescriptor.getOutsideVisibleReferencesCount();
368 // count implemented interfaces
369 for (final ClassDescriptor classDescriptor : interfaces)
370 if (classDescriptor.isVisible())
374 if (superClass != null)
375 if (superClass.isVisible())
381 private String getPackageName() {
383 final int i = fullyQualifiedName.lastIndexOf('.');
388 return fullyQualifiedName.substring(0, i).replace("[L", "");
391 private String getParentClassesName() {
392 int i = fullyQualifiedName.lastIndexOf('.');
393 final String fullClassName = fullyQualifiedName.substring(i + 1);
395 i = fullClassName.lastIndexOf('$');
398 final String parentClassesName = fullClassName.substring(0, i);
399 return parentClassesName.replace('$', '.');
402 private String getSuperClassColor() {
403 if (superClassColor == null)
404 superClassColor = Utils.getNextLightColor();
406 return superClassColor;
410 * Checks if class has field with given name (case is ignored). Returns
411 * <code>true</code> if such field is found.
413 * @param fieldToSearch field to search for (case is ignored)
414 * @return <code>true</code> if field is found.
416 protected boolean hasFieldIgnoreCase(final String fieldToSearch) {
418 for (final String fieldName : nameToFieldMap.keySet())
419 if (fieldToSearch.equalsIgnoreCase(fieldName))
425 private void hide() {
429 protected void hideClassIfNoReferences() {
433 final int totalReferencesCount = getOutgoingReferencesCount()
434 + referencesCount + extensionsCount + implementationsCount;
436 if (totalReferencesCount == 0) {
444 private void indexFields(final Field[] fields) {
445 for (final Field field : fields)
446 getOrCreateFieldDescriptor(field);
449 private void indexMethods(final Class<? extends Object> clazz) {
450 for (final Method method : clazz.getMethods()) {
451 final MethodDescriptor methodDescriptor = new MethodDescriptor(
452 this, method.getName());
454 methods.add(methodDescriptor);
456 methodDescriptor.analyze(method);
462 public boolean isVisible() {
464 if (Utils.isSystemDataType(fullyQualifiedName))
467 if (Utils.isSystemPackage(fullyQualifiedName))
470 if (!getClassGraph().isClassShown(fullyQualifiedName))
474 if (arrayComponent != null)
475 if (Utils.isSystemDataType(arrayComponent.fullyQualifiedName))
476 // Do not show references to primitive data types in arrays.
477 // That is: there is no point to show reference to byte when
478 // we have class with byte array field.
485 * Register event when another class is extending this one.
487 protected void registerExtension() {
491 protected void registerImplementation() {
492 implementationsCount++;
495 protected void registerReference() {
500 public boolean equals(Object o) {
501 if (this == o) return true;
502 if (!(o instanceof ClassDescriptor)) return false;
504 ClassDescriptor that = (ClassDescriptor) o;
506 return getFullyQualifiedName().equals(that.getFullyQualifiedName());
511 public int hashCode() {
512 return getFullyQualifiedName().hashCode();
516 public int compareTo(ClassDescriptor o) {
517 return fullyQualifiedName.compareTo(o.fullyQualifiedName);