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