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;
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();
76 indexFields(clazz.getDeclaredFields());
77 indexFields(clazz.getFields());
81 for (final Class interfaceClass : clazz.getInterfaces()) {
82 final ClassDescriptor interfaceClassDescriptor = getClassGraph()
83 .getOrCreateClassDescriptor(interfaceClass);
84 interfaceClassDescriptor.registerImplementation();
85 interfaces.add(interfaceClassDescriptor);
88 superClass = getClassGraph().getOrCreateClassDescriptor(
89 clazz.getSuperclass());
90 if (superClass != null)
91 superClass.registerExtension();
95 protected boolean areReferencesShown() {
96 return referencesCount <= MAX_REFERECNES_COUNT;
99 private void enlistFieldReferences(final StringBuffer result) {
100 if (nameToFieldMap.isEmpty())
104 result.append(" // field references to other classes\n");
105 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
107 result.append(entry.getValue().getDot());
110 private void enlistFields(final StringBuffer result) {
111 if (nameToFieldMap.isEmpty())
115 result.append(" // fields:\n");
118 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
120 result.append(entry.getValue().getEmbeddedDot());
123 private void enlistImplementedInterfaces(final StringBuffer result) {
124 if (interfaces.isEmpty())
128 result.append(" // interfaces implemented by class: "
129 + fullyQualifiedName + "\n");
131 for (final ClassDescriptor interfaceDescriptor : interfaces) {
132 if (!interfaceDescriptor.isVisible())
135 if (!interfaceDescriptor.areReferencesShown())
138 result.append(" " + interfaceDescriptor.getGraphId() + " -> "
139 + getGraphId() + "[style=\"dotted\", color=\""
140 + interfaceDescriptor.getInterfaceColor()
141 + "\", penwidth=10, dir=\"forward\"];\n");
145 private void enlistMethodReferences(final StringBuffer result) {
146 if (methods.isEmpty())
150 result.append(" // method references to other classes\n");
151 for (final MethodDescriptor methodDescriptor : methods)
152 result.append(methodDescriptor.getDot());
155 private void enlistMethods(final StringBuffer result) {
156 if (methods.isEmpty())
160 result.append(" // methods:\n");
163 for (final MethodDescriptor methodDescriptor : methods)
164 result.append(methodDescriptor.getEmbeddedDot());
167 private void enlistSuperClass(final StringBuffer result) {
168 if (superClass == null)
171 if (!superClass.isVisible())
174 if (!superClass.areReferencesShown())
178 result.append(" // super class for: " + fullyQualifiedName + "\n");
180 result.append(" " + superClass.getGraphId() + " -> " + getGraphId()
181 + "[ color=\"" + superClass.getSuperClassColor()
182 + "\", penwidth=10, dir=\"forward\"];\n");
185 private void generateDotHeader(final StringBuffer result) {
187 result.append("// Class: " + fullyQualifiedName + "\n");
189 result.append(" " + getGraphId() + "[label=<<TABLE "
190 + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
191 + "\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
194 result.append(" // class descriptor header\n");
195 result.append(" <TR><TD colspan=\"2\" PORT=\"f0\">"
196 + "<FONT POINT-SIZE=\"8.0\" >" + getPackageName()
199 final String parentClassesName = getParentClassesName();
200 if (parentClassesName.length() > 0)
201 result.append("<FONT POINT-SIZE=\"12.0\"><B>" + parentClassesName
202 + "</B></FONT><br/>\n");
204 result.append("<FONT POINT-SIZE=\"25.0\"><B>" + getClassName(false)
205 + "</B></FONT>" + "</TD></TR>\n");
208 private String getBackgroundColor() {
212 bgColor = "bgcolor=\"navajowhite2\"";
215 bgColor = "bgcolor=\"darkslategray1\"";
220 private String getBorderWidth() {
222 if (!areReferencesShown())
227 protected ClassGraph getClassGraph() {
231 protected String getClassName(final boolean differentiateArray) {
232 // this is needed for nested classes
233 final String actualClassName = fullyQualifiedName.replace('$', '.');
237 // for arrays use array component instead of array class name
238 result = arrayComponent.fullyQualifiedName;
239 if (result.contains(".")) {
240 final int i = result.lastIndexOf('.');
241 result = result.substring(i + 1);
244 final int i = actualClassName.lastIndexOf('.');
245 result = actualClassName.substring(i + 1);
248 if (differentiateArray)
252 // this is needed for nested classes
253 // result = result.replace('$', '.');
257 protected String getColor() {
258 if (distinctiveReferenceColor == null)
259 distinctiveReferenceColor = Utils.getNextDarkColor();
261 return distinctiveReferenceColor;
265 public String getDot() {
272 final StringBuffer result = new StringBuffer();
274 generateDotHeader(result);
276 enlistFields(result);
278 enlistMethods(result);
280 result.append(" </TABLE>>, shape=\"none\"];\n");
282 enlistFieldReferences(result);
284 enlistMethodReferences(result);
286 enlistImplementedInterfaces(result);
288 enlistSuperClass(result);
290 return result.toString();
294 public String getEmbeddedDot() {
299 * Returns field with given name (case is ignored). Or <code>null</code> if
300 * field is not found.
302 * @param fieldToSearch field name (case is ignored)
303 * @return field matching given name
305 protected FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
307 for (final String fieldName : nameToFieldMap.keySet())
308 if (fieldToSearch.equalsIgnoreCase(fieldName))
309 return nameToFieldMap.get(fieldName);
314 protected String getFullyQualifiedName() {
315 return fullyQualifiedName;
319 public String getGraphId() {
320 final String result = "class_"
330 private String getInterfaceColor() {
331 if (interfaceColor == null)
332 interfaceColor = Utils.getNextDarkColor();
334 return interfaceColor;
337 private FieldDescriptor getOrCreateFieldDescriptor(final Field field) {
339 final String fieldName = field.getName();
341 if (nameToFieldMap.containsKey(fieldName))
342 return nameToFieldMap.get(fieldName);
344 final FieldDescriptor newFieldDescriptor = new FieldDescriptor(this);
345 nameToFieldMap.put(fieldName, newFieldDescriptor);
347 newFieldDescriptor.analyzeField(field);
349 return newFieldDescriptor;
352 private int getOutgoingReferencesCount() {
355 // count method references
356 for (final MethodDescriptor methodDescriptor : methods)
357 result += methodDescriptor.getOutsideVisibleReferencesCount();
359 // count field references
360 for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
361 result += fieldDescriptor.getOutsideVisibleReferencesCount();
363 // count implemented interfaces
364 for (final ClassDescriptor classDescriptor : interfaces)
365 if (classDescriptor.isVisible())
369 if (superClass != null)
370 if (superClass.isVisible())
376 private String getPackageName() {
378 final int i = fullyQualifiedName.lastIndexOf('.');
383 return fullyQualifiedName.substring(0, i).replace("[L", "");
386 private String getParentClassesName() {
387 int i = fullyQualifiedName.lastIndexOf('.');
388 final String fullClassName = fullyQualifiedName.substring(i + 1);
390 i = fullClassName.lastIndexOf('$');
393 final String parentClassesName = fullClassName.substring(0, i);
394 return parentClassesName.replace('$', '.');
397 private String getSuperClassColor() {
398 if (superClassColor == null)
399 superClassColor = Utils.getNextLightColor();
401 return superClassColor;
405 * Checks if class has field with given name (case is ignored). Returns
406 * <code>true</code> if such field is found.
408 * @param fieldToSearch field to search for (case is ignored)
409 * @return <code>true</code> if field is found.
411 protected boolean hasFieldIgnoreCase(final String fieldToSearch) {
413 for (final String fieldName : nameToFieldMap.keySet())
414 if (fieldToSearch.equalsIgnoreCase(fieldName))
420 private void hide() {
424 protected void hideClassIfNoReferences() {
428 final int totalReferencesCount = getOutgoingReferencesCount()
429 + referencesCount + extensionsCount + implementationsCount;
431 if (totalReferencesCount == 0) {
439 private void indexFields(final Field[] fields) {
440 for (final Field field : fields)
441 getOrCreateFieldDescriptor(field);
444 private void indexMethods(final Class<? extends Object> clazz) {
445 for (final Method method : clazz.getMethods()) {
446 final MethodDescriptor methodDescriptor = new MethodDescriptor(
447 this, method.getName());
449 methods.add(methodDescriptor);
451 methodDescriptor.analyze(method);
457 public boolean isVisible() {
459 if (Utils.isSystemDataType(fullyQualifiedName))
462 if (Utils.isSystemPackage(fullyQualifiedName))
465 if (!getClassGraph().isClassShown(fullyQualifiedName))
469 if (arrayComponent != null)
470 if (Utils.isSystemDataType(arrayComponent.fullyQualifiedName))
471 // Do not show references to primitive data types in arrays.
472 // That is: there is no point to show reference to byte when
473 // we have class with byte array field.
480 * Register event when another class is extending this one.
482 protected void registerExtension() {
486 protected void registerImplementation() {
487 implementationsCount++;
490 protected void registerReference() {
495 public boolean equals(Object o) {
496 if (this == o) return true;
497 if (!(o instanceof ClassDescriptor)) return false;
499 ClassDescriptor that = (ClassDescriptor) o;
501 return getFullyQualifiedName().equals(that.getFullyQualifiedName());
506 public int hashCode() {
507 return getFullyQualifiedName().hashCode();
511 public int compareTo(ClassDescriptor o) {
512 return fullyQualifiedName.compareTo(o.fullyQualifiedName);