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