count class extensions as references when calculating orphaned classes
[javainspect.git] / src / main / java / eu / svjatoslav / inspector / java / structure / ClassDescriptor.java
1 /*
2  * JavaInspect - Utility to visualize java software
3  * Copyright (C) 2013, 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 2 of the GNU General Public License
7  * 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 fullyQualifiedName;
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 incomingReferencesCount = 0;
61
62         private int extensionsCount = 0;
63
64         public ClassDescriptor(final Class<? extends Object> clazz,
65                         final ClassGraph dump) {
66                 classGraph = dump;
67
68                 fullyQualifiedName = clazz.getName();
69                 dump.nameToClassMap.put(fullyQualifiedName, this);
70
71                 isArray = clazz.isArray();
72
73                 if (isArray) {
74                         final Class<?> componentType = clazz.getComponentType();
75                         dump.addClass(componentType);
76                 }
77
78                 // System.out.println("class: " + fullyQualifiedName);
79
80                 isEnum = clazz.isEnum();
81
82                 isInterface = clazz.isInterface();
83
84                 if (!isVisible())
85                         return;
86
87                 indexFields(clazz.getDeclaredFields());
88                 indexFields(clazz.getFields());
89
90                 indexMethods(clazz);
91
92                 for (final Class interfaceClass : clazz.getInterfaces()) {
93                         final ClassDescriptor classDescriptor = dump
94                                         .addClass(interfaceClass);
95                         classDescriptor.registerExtension();
96                         interfaces.add(classDescriptor);
97                 }
98
99                 superClass = dump.addClass(clazz.getSuperclass());
100                 if (superClass != null)
101                         superClass.registerExtension();
102
103         }
104
105         public boolean areReferencesShown() {
106                 return incomingReferencesCount <= MAX_REFERECNES_COUNT;
107         }
108
109         public void enlistFieldReferences(final StringBuffer result) {
110                 if (nameToFieldMap.isEmpty())
111                         return;
112
113                 result.append("\n");
114                 result.append("    // field references to other classes\n");
115                 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
116                                 .entrySet())
117                         result.append(entry.getValue().getDot());
118         }
119
120         public void enlistFields(final StringBuffer result) {
121                 if (nameToFieldMap.isEmpty())
122                         return;
123
124                 result.append("\n");
125                 result.append("    // fields:\n");
126
127                 // enlist fields
128                 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
129                                 .entrySet())
130                         result.append(entry.getValue().getEmbeddedDot());
131         }
132
133         public void enlistImplementedInterfaces(final StringBuffer result) {
134                 if (interfaces.isEmpty())
135                         return;
136
137                 result.append("\n");
138                 result.append("    // interfaces implemented by class: "
139                                 + fullyQualifiedName + "\n");
140
141                 for (final ClassDescriptor interfaceDescriptor : interfaces) {
142                         if (!interfaceDescriptor.isVisible())
143                                 continue;
144
145                         result.append("    " + interfaceDescriptor.getGraphId() + " -> "
146                                         + getGraphId() + "[style=\"dotted, tapered\", color=\""
147                                         + interfaceDescriptor.getInterfaceColor()
148                                         + "\", penwidth=20, dir=\"forward\"];\n");
149                 }
150         }
151
152         public void enlistMethodReferences(final StringBuffer result) {
153                 if (methods.isEmpty())
154                         return;
155
156                 result.append("\n");
157                 result.append("    // method references to other classes\n");
158                 for (final MethodDescriptor methodDescriptor : methods)
159                         result.append(methodDescriptor.getDot());
160         }
161
162         public void enlistMethods(final StringBuffer result) {
163                 if (methods.isEmpty())
164                         return;
165
166                 result.append("\n");
167                 result.append("    // methods:\n");
168
169                 // enlist methods
170                 for (final MethodDescriptor methodDescriptor : methods)
171                         result.append(methodDescriptor.getEmbeddedDot());
172         }
173
174         public void enlistSuperClass(final StringBuffer result) {
175                 if (superClass == null)
176                         return;
177
178                 if (!superClass.isVisible())
179                         return;
180
181                 result.append("\n");
182                 result.append("    // super class for: " + fullyQualifiedName + "\n");
183
184                 result.append("    " + superClass.getGraphId() + " -> " + getGraphId()
185                                 + "[style=\"tapered\", color=\""
186                                 + superClass.getSuperClassColor()
187                                 + "\", penwidth=10, dir=\"forward\"];\n");
188         }
189
190         public void generateDotHeader(final StringBuffer result) {
191                 result.append("\n");
192                 result.append("// Class: " + fullyQualifiedName + "\n");
193
194                 result.append("    " + getGraphId() + "[label=<<TABLE "
195                                 + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
196                                 + "\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
197
198                 result.append("\n");
199                 result.append("    // class descriptor header\n");
200                 result.append("    <TR><TD colspan=\"2\" PORT=\"f0\">"
201                                 + "<FONT POINT-SIZE=\"8.0\" >" + getPackageName()
202                                 + "</FONT><br/>");
203
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");
208
209                 result.append("<FONT POINT-SIZE=\"25.0\"><B>" + getClassName(false)
210                                 + "</B></FONT>" + "</TD></TR>\n");
211         }
212
213         public List<FieldDescriptor> getAllFields() {
214                 final List<FieldDescriptor> result = new ArrayList<FieldDescriptor>();
215
216                 for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
217                                 .entrySet())
218                         result.add(entry.getValue());
219
220                 return result;
221         }
222
223         public String getBackgroundColor() {
224                 String bgColor = "";
225
226                 if (isEnum)
227                         bgColor = "bgcolor=\"navajowhite2\"";
228
229                 if (isInterface)
230                         bgColor = "bgcolor=\"darkslategray1\"";
231
232                 return bgColor;
233         }
234
235         public String getBorderWidth() {
236
237                 if (!areReferencesShown())
238                         return "4";
239                 return "1";
240         }
241
242         public String getClassName(final boolean differentiateArray) {
243                 // this is needed for nested classes
244                 final String actualClassName = fullyQualifiedName.replace('$', '.');
245
246                 final int i = actualClassName.lastIndexOf('.');
247
248                 String result = actualClassName.substring(i + 1);
249
250                 if (isArray)
251                         result = result.substring(0, result.length() - 1);
252
253                 if (differentiateArray)
254                         if (isArray)
255                                 result += " []";
256
257                 // this is needed for nested classes
258                 // result = result.replace('$', '.');
259                 return result;
260         }
261
262         public String getColor() {
263                 if (getDistinctiveColor() == null)
264                         setDistinctiveColor(Utils.getNextDarkColor());
265
266                 return getDistinctiveColor();
267         }
268
269         public String getDistinctiveColor() {
270                 return distinctiveReferenceColor;
271         }
272
273         @Override
274         public String getDot() {
275                 if (!isVisible())
276                         return "";
277
278                 if (isArray)
279                         return "";
280
281                 final StringBuffer result = new StringBuffer();
282
283                 generateDotHeader(result);
284
285                 enlistFields(result);
286
287                 enlistMethods(result);
288
289                 result.append("    </TABLE>>, shape=\"none\"];\n");
290
291                 enlistFieldReferences(result);
292
293                 enlistMethodReferences(result);
294
295                 enlistImplementedInterfaces(result);
296
297                 enlistSuperClass(result);
298
299                 return result.toString();
300         }
301
302         @Override
303         public String getEmbeddedDot() {
304                 return null;
305         }
306
307         @Override
308         public String getGraphId() {
309                 final String result = "class_"
310                                 + fullyQualifiedName.replace('.', '_').replace(";", "")
311                                                 .replace("[L", "").replace('$', '_');
312                 return result;
313         }
314
315         public String getInterfaceColor() {
316                 if (interfaceColor == null)
317                         interfaceColor = Utils.getNextLightColor();
318
319                 return interfaceColor;
320         }
321
322         private int getOutgoingReferencesCount() {
323                 int result = 0;
324
325                 // count method references
326                 for (final MethodDescriptor methodDescriptor : methods)
327                         result += methodDescriptor.getOutsideVisibleReferencesCount();
328
329                 // count field references
330                 for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
331                         result += fieldDescriptor.getOutsideVisibleReferencesCount();
332
333                 // count implemented interfaces
334                 for (final ClassDescriptor classDescriptor : interfaces)
335                         if (classDescriptor.isVisible())
336                                 result++;
337
338                 // count superclass
339                 if (superClass != null)
340                         if (superClass.isVisible())
341                                 result++;
342
343                 return result;
344         }
345
346         public String getPackageName() {
347
348                 final int i = fullyQualifiedName.lastIndexOf('.');
349
350                 if (i == -1)
351                         return "";
352
353                 return fullyQualifiedName.substring(0, i).replace("[L", "");
354         }
355
356         // public String getReadableName() {
357         //
358         // // do not print full class name for well known system classes
359         // final String packageName = getPackageName();
360         //
361         // if (packageName.equals("java.util"))
362         // return getClassName();
363         //
364         // if (packageName.equals("java.lang"))
365         // return getClassName();
366         //
367         // return fullyQualifiedName;
368         // }
369
370         public String getParentClassesName() {
371                 int i = fullyQualifiedName.lastIndexOf('.');
372                 final String fullClassName = fullyQualifiedName.substring(i + 1);
373
374                 i = fullClassName.lastIndexOf('$');
375                 if (i == -1)
376                         return "";
377                 final String parentClassesName = fullClassName.substring(0, i);
378                 return parentClassesName.replace('$', '.');
379         }
380
381         public String getSuperClassColor() {
382                 if (superClassColor == null)
383                         superClassColor = Utils.getNextLightColor();
384
385                 return superClassColor;
386         }
387
388         public void hide() {
389                 isShown = false;
390         }
391
392         public void hideClassIfNoReferences() {
393                 if (!isVisible())
394                         return;
395
396                 final int totalReferencesCount = getOutgoingReferencesCount()
397                                 + incomingReferencesCount + extensionsCount;
398
399                 if (totalReferencesCount == 0) {
400                         hide();
401                         return;
402                 }
403
404                 return;
405         }
406
407         public void indexFields(final Field[] fields) {
408                 for (final Field field : fields) {
409                         if (nameToFieldMap.containsKey(field.getName()))
410                                 continue;
411
412                         final FieldDescriptor fieldDescriptor = new FieldDescriptor(field,
413                                         this, classGraph);
414
415                 }
416         }
417
418         private void indexMethods(final Class<? extends Object> clazz) {
419                 final Method[] methods = clazz.getMethods();
420
421                 for (final Method method : methods)
422                         new MethodDescriptor(method, this, classGraph);
423
424         }
425
426         @Override
427         public boolean isVisible() {
428
429                 if (Utils.isSystemDataType(fullyQualifiedName))
430                         return false;
431
432                 if (Utils.isSystemPackage(fullyQualifiedName))
433                         return false;
434
435                 if (!classGraph.getFilter().isClassShown(fullyQualifiedName))
436                         return false;
437
438                 return isShown;
439         }
440
441         /**
442          * Register event when another class is extending this one, or is
443          * implementing interface declared by this class.
444          */
445         public void registerExtension() {
446                 extensionsCount++;
447         }
448
449         public void registerReference() {
450                 incomingReferencesCount++;
451         }
452
453         public void setDistinctiveColor(final String distinctiveColor) {
454                 distinctiveReferenceColor = distinctiveColor;
455         }
456 }