hide parent class and interface class references if there are too many of them
[javainspect.git] / src / main / java / eu / svjatoslav / inspector / java / structure / ClassDescriptor.java
1 /*
2  * JavaInspect - Utility to visualize java software
3  * Copyright (C) 2013-2014, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4  *
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.
8  */
9
10 package eu.svjatoslav.inspector.java.structure;
11
12 import java.lang.reflect.Field;
13 import java.lang.reflect.Method;
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.SortedSet;
18 import java.util.TreeMap;
19 import java.util.TreeSet;
20
21 /**
22  * Describes single class instance
23  */
24 public class ClassDescriptor implements GraphElement {
25
26         private static final int MAX_REFERECNES_COUNT = 10;
27
28         public final String classFullyQualifiedName;
29
30         Map<String, FieldDescriptor> nameToFieldMap = new TreeMap<String, FieldDescriptor>();
31
32         public SortedSet<MethodDescriptor> methods = new TreeSet<MethodDescriptor>();
33
34         /**
35          * Incoming arrows will have this color.
36          */
37         private String distinctiveReferenceColor;
38
39         private String interfaceColor;
40
41         private String superClassColor;
42
43         boolean isEnum;
44
45         boolean isInterface;
46
47         boolean isArray;
48
49         private boolean isShown = true;
50
51         private final ClassGraph classGraph;
52
53         List<ClassDescriptor> interfaces = new ArrayList<ClassDescriptor>();
54
55         ClassDescriptor superClass;
56
57         /**
58          * Amount of field and method references pointing to this class.
59          */
60         private int referencesCount = 0;
61
62         // for interface, counts amount of found implementations
63         private int implementationsCount = 0;
64
65         // counts amount of times this class is extended
66         private int extensionsCount = 0;
67
68         private ClassDescriptor arrayComponent;
69
70         public ClassDescriptor(final Class<? extends Object> clazz,
71                         final ClassGraph classGraph) {
72                 this.classGraph = classGraph;
73
74                 classFullyQualifiedName = clazz.getName();
75
76                 classGraph.registerClass(classFullyQualifiedName, this);
77
78                 isArray = clazz.isArray();
79
80                 if (isArray) {
81                         final Class<?> componentType = clazz.getComponentType();
82                         arrayComponent = classGraph.addClass(componentType);
83                 }
84
85                 // System.out.println("class: " + fullyQualifiedName);
86
87                 isEnum = clazz.isEnum();
88
89                 isInterface = clazz.isInterface();
90
91                 if (!isVisible())
92                         return;
93
94                 indexFields(clazz.getDeclaredFields());
95                 indexFields(clazz.getFields());
96
97                 indexMethods(clazz);
98
99                 for (final Class interfaceClass : clazz.getInterfaces()) {
100                         final ClassDescriptor classDescriptor = classGraph
101                                         .addClass(interfaceClass);
102                         classDescriptor.registerImplementation();
103                         interfaces.add(classDescriptor);
104                 }
105
106                 superClass = classGraph.addClass(clazz.getSuperclass());
107                 if (superClass != null)
108                         superClass.registerExtension();
109
110         }
111
112         public boolean areReferencesShown() {
113                 return referencesCount <= MAX_REFERECNES_COUNT;
114         }
115
116         public void enlistFieldReferences(final StringBuffer result) {
117                 if (nameToFieldMap.isEmpty())
118                         return;
119
120                 result.append("\n");
121                 result.append("    // field references to other classes\n");
122                 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
123                                 .entrySet())
124                         result.append(entry.getValue().getDot());
125         }
126
127         public void enlistFields(final StringBuffer result) {
128                 if (nameToFieldMap.isEmpty())
129                         return;
130
131                 result.append("\n");
132                 result.append("    // fields:\n");
133
134                 // enlist fields
135                 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
136                                 .entrySet())
137                         result.append(entry.getValue().getEmbeddedDot());
138         }
139
140         public void enlistImplementedInterfaces(final StringBuffer result) {
141                 if (interfaces.isEmpty())
142                         return;
143
144                 result.append("\n");
145                 result.append("    // interfaces implemented by class: "
146                                 + classFullyQualifiedName + "\n");
147
148                 for (final ClassDescriptor interfaceDescriptor : interfaces) {
149                         if (!interfaceDescriptor.isVisible())
150                                 continue;
151
152                         if (!interfaceDescriptor.areReferencesShown())
153                                 continue;
154
155                         result.append("    " + interfaceDescriptor.getGraphId() + " -> "
156                                         + getGraphId() + "[style=\"dotted, tapered\", color=\""
157                                         + interfaceDescriptor.getInterfaceColor()
158                                         + "\", penwidth=20, dir=\"forward\"];\n");
159                 }
160         }
161
162         public void enlistMethodReferences(final StringBuffer result) {
163                 if (methods.isEmpty())
164                         return;
165
166                 result.append("\n");
167                 result.append("    // method references to other classes\n");
168                 for (final MethodDescriptor methodDescriptor : methods)
169                         result.append(methodDescriptor.getDot());
170         }
171
172         public void enlistMethods(final StringBuffer result) {
173                 if (methods.isEmpty())
174                         return;
175
176                 result.append("\n");
177                 result.append("    // methods:\n");
178
179                 // enlist methods
180                 for (final MethodDescriptor methodDescriptor : methods)
181                         result.append(methodDescriptor.getEmbeddedDot());
182         }
183
184         public void enlistSuperClass(final StringBuffer result) {
185                 if (superClass == null)
186                         return;
187
188                 if (!superClass.isVisible())
189                         return;
190
191                 if (!superClass.areReferencesShown())
192                         return;
193
194                 result.append("\n");
195                 result.append("    // super class for: " + classFullyQualifiedName
196                                 + "\n");
197
198                 result.append("    " + superClass.getGraphId() + " -> " + getGraphId()
199                                 + "[style=\"tapered\", color=\""
200                                 + superClass.getSuperClassColor()
201                                 + "\", penwidth=10, dir=\"forward\"];\n");
202         }
203
204         public void generateDotHeader(final StringBuffer result) {
205                 result.append("\n");
206                 result.append("// Class: " + classFullyQualifiedName + "\n");
207
208                 result.append("    " + getGraphId() + "[label=<<TABLE "
209                                 + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
210                                 + "\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
211
212                 result.append("\n");
213                 result.append("    // class descriptor header\n");
214                 result.append("    <TR><TD colspan=\"2\" PORT=\"f0\">"
215                                 + "<FONT POINT-SIZE=\"8.0\" >" + getPackageName()
216                                 + "</FONT><br/>");
217
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");
222
223                 result.append("<FONT POINT-SIZE=\"25.0\"><B>" + getClassName(false)
224                                 + "</B></FONT>" + "</TD></TR>\n");
225         }
226
227         public List<FieldDescriptor> getAllFields() {
228                 final List<FieldDescriptor> result = new ArrayList<FieldDescriptor>();
229
230                 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
231                                 .entrySet())
232                         result.add(entry.getValue());
233
234                 return result;
235         }
236
237         public String getBackgroundColor() {
238                 String bgColor = "";
239
240                 if (isEnum)
241                         bgColor = "bgcolor=\"navajowhite2\"";
242
243                 if (isInterface)
244                         bgColor = "bgcolor=\"darkslategray1\"";
245
246                 return bgColor;
247         }
248
249         public String getBorderWidth() {
250
251                 if (!areReferencesShown())
252                         return "4";
253                 return "1";
254         }
255
256         public String getClassName(final boolean differentiateArray) {
257                 // this is needed for nested classes
258                 final String actualClassName = classFullyQualifiedName
259                                 .replace('$', '.');
260
261                 String result;
262                 if (isArray) {
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);
268                         }
269                 } else {
270                         final int i = actualClassName.lastIndexOf('.');
271                         result = actualClassName.substring(i + 1);
272                 }
273
274                 if (differentiateArray)
275                         if (isArray)
276                                 result += " []";
277
278                 // this is needed for nested classes
279                 // result = result.replace('$', '.');
280                 return result;
281         }
282
283         public String getColor() {
284                 if (getDistinctiveColor() == null)
285                         setDistinctiveColor(Utils.getNextDarkColor());
286
287                 return getDistinctiveColor();
288         }
289
290         public String getDistinctiveColor() {
291                 return distinctiveReferenceColor;
292         }
293
294         @Override
295         public String getDot() {
296                 if (!isVisible())
297                         return "";
298
299                 if (isArray)
300                         return "";
301
302                 final StringBuffer result = new StringBuffer();
303
304                 generateDotHeader(result);
305
306                 enlistFields(result);
307
308                 enlistMethods(result);
309
310                 result.append("    </TABLE>>, shape=\"none\"];\n");
311
312                 enlistFieldReferences(result);
313
314                 enlistMethodReferences(result);
315
316                 enlistImplementedInterfaces(result);
317
318                 enlistSuperClass(result);
319
320                 return result.toString();
321         }
322
323         @Override
324         public String getEmbeddedDot() {
325                 return null;
326         }
327
328         /**
329          * Returns field with given name (case is ignored). Or <code>null</code> if
330          * field is not found.
331          */
332         public FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
333
334                 for (final String fieldName : nameToFieldMap.keySet())
335                         if (fieldToSearch.equalsIgnoreCase(fieldName))
336                                 return nameToFieldMap.get(fieldName);
337
338                 return null;
339         }
340
341         @Override
342         public String getGraphId() {
343                 final String result = "class_"
344                                 + classFullyQualifiedName.replace('.', '_').replace(";", "")
345                                 .replace("[L", "").replace('$', '_');
346                 return result;
347         }
348
349         public String getInterfaceColor() {
350                 if (interfaceColor == null)
351                         interfaceColor = Utils.getNextLightColor();
352
353                 return interfaceColor;
354         }
355
356         private int getOutgoingReferencesCount() {
357                 int result = 0;
358
359                 // count method references
360                 for (final MethodDescriptor methodDescriptor : methods)
361                         result += methodDescriptor.getOutsideVisibleReferencesCount();
362
363                 // count field references
364                 for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
365                         result += fieldDescriptor.getOutsideVisibleReferencesCount();
366
367                 // count implemented interfaces
368                 for (final ClassDescriptor classDescriptor : interfaces)
369                         if (classDescriptor.isVisible())
370                                 result++;
371
372                 // count superclass
373                 if (superClass != null)
374                         if (superClass.isVisible())
375                                 result++;
376
377                 return result;
378         }
379
380         // public String getReadableName() {
381         //
382         // // do not print full class name for well known system classes
383         // final String packageName = getPackageName();
384         //
385         // if (packageName.equals("java.util"))
386         // return getClassName();
387         //
388         // if (packageName.equals("java.lang"))
389         // return getClassName();
390         //
391         // return fullyQualifiedName;
392         // }
393
394         public String getPackageName() {
395
396                 final int i = classFullyQualifiedName.lastIndexOf('.');
397
398                 if (i == -1)
399                         return "";
400
401                 return classFullyQualifiedName.substring(0, i).replace("[L", "");
402         }
403
404         public String getParentClassesName() {
405                 int i = classFullyQualifiedName.lastIndexOf('.');
406                 final String fullClassName = classFullyQualifiedName.substring(i + 1);
407
408                 i = fullClassName.lastIndexOf('$');
409                 if (i == -1)
410                         return "";
411                 final String parentClassesName = fullClassName.substring(0, i);
412                 return parentClassesName.replace('$', '.');
413         }
414
415         public String getSuperClassColor() {
416                 if (superClassColor == null)
417                         superClassColor = Utils.getNextLightColor();
418
419                 return superClassColor;
420         }
421
422         /**
423          * Checks if class has field with given name (case is ignored). Returns
424          * <code>true</code> if such field is found.
425          */
426         public boolean hasFieldIgnoreCase(final String fieldToSearch) {
427
428                 for (final String fieldName : nameToFieldMap.keySet())
429                         if (fieldToSearch.equalsIgnoreCase(fieldName))
430                                 return true;
431
432                 return false;
433         }
434
435         public void hide() {
436                 isShown = false;
437         }
438
439         public void hideClassIfNoReferences() {
440                 if (!isVisible())
441                         return;
442
443                 final int totalReferencesCount = getOutgoingReferencesCount()
444                                 + referencesCount + extensionsCount + implementationsCount;
445
446                 if (totalReferencesCount == 0) {
447                         hide();
448                         return;
449                 }
450
451                 return;
452         }
453
454         public void indexFields(final Field[] fields) {
455                 for (final Field field : fields) {
456                         if (nameToFieldMap.containsKey(field.getName()))
457                                 continue;
458
459                         final FieldDescriptor fieldDescriptor = new FieldDescriptor(field,
460                                         this, classGraph);
461
462                 }
463         }
464
465         private void indexMethods(final Class<? extends Object> clazz) {
466                 final Method[] methods = clazz.getMethods();
467
468                 for (final Method method : methods)
469                         new MethodDescriptor(method, this, classGraph);
470
471         }
472
473         @Override
474         public boolean isVisible() {
475
476                 if (Utils.isSystemDataType(classFullyQualifiedName))
477                         return false;
478
479                 if (Utils.isSystemPackage(classFullyQualifiedName))
480                         return false;
481
482                 if (!classGraph.getFilter().isClassShown(classFullyQualifiedName))
483                         return false;
484
485                 if (isArray)
486                         if (arrayComponent != null)
487                                 if (Utils
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.
492                                         return false;
493
494                 return isShown;
495         }
496
497         /**
498          * Register event when another class is extending this one.
499          */
500         public void registerExtension() {
501                 extensionsCount++;
502         }
503
504         public void registerImplementation() {
505                 implementationsCount++;
506         }
507
508         public void registerReference() {
509                 referencesCount++;
510         }
511
512         public void setDistinctiveColor(final String distinctiveColor) {
513                 distinctiveReferenceColor = distinctiveColor;
514         }
515 }