├── .gitignore ├── .idea └── workspace.xml ├── LICENSE ├── META-INF └── MANIFEST.MF ├── README.md ├── Report.pdf ├── pom.xml └── src └── main ├── java ├── Runner.java └── analyzer │ ├── AbstractVoidVisitorAdapter.java │ ├── Analyzer.java │ ├── ComplexityCounter.java │ ├── Config.java │ ├── collectors │ ├── Collector.java │ └── HashMapCollector.java │ ├── printers │ ├── AbstractPrinter.java │ ├── ComplexityPrinter.java │ ├── MetricPrinter.java │ ├── Printable.java │ └── WarningPrinter.java │ └── visitors │ ├── BooleanMethodVisitor.java │ ├── ClassComplexityVisitor.java │ ├── ClassLineCounterVisitor.java │ ├── MetricVisitor.java │ ├── TooLongVisitor.java │ ├── TooSmallVisitor.java │ └── VariableNamingConventionVisitor.java └── resources └── Test.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | target -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 160 | 161 | 162 | 163 | 164 | true 165 | DEFINITION_ORDER 166 | 167 | 168 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 316 | 317 | 318 | 319 | 320 | 332 | 333 | 334 | 346 | 347 | 348 | 360 | 361 | 362 | 374 | 375 | 376 | 377 | 382 | 396 | 397 | 398 | 399 | 400 | 406 | 407 | 408 | 420 | 421 | 442 | 454 | 455 | 464 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | project 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 501 | 502 | 503 | 504 | 505 | 506 | 510 | 511 | 517 | 518 | 519 | 520 | 538 | 544 | 545 | 546 | 563 | 564 | 565 | 581 | 582 | 583 | 584 | 589 | 607 | 614 | 615 | 616 | 633 | 634 | 635 | 642 | 645 | 647 | 648 | 649 | 650 | 651 | 652 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 691 | 692 | 693 | 694 | 695 | 696 | 707 | 708 | 709 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 749 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | project 765 | 766 | 767 | true 768 | 769 | bdd 770 | 771 | DIRECTORY 772 | 773 | false 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 1475829258323 807 | 820 | 821 | 1476548407331 822 | 827 | 828 | 1476560176894 829 | 834 | 835 | 1477165686875 836 | 841 | 842 | 1477261703163 843 | 848 | 849 | 1477262386946 850 | 855 | 856 | 1507835142276 857 | 862 | 865 | 866 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 933 | 934 | 937 | 940 | 941 | 942 | 944 | 945 | 955 | 956 | 957 | 958 | 959 | file://$PROJECT_DIR$/src/main/java/analyzer/collectors/HashMapCollector.java 960 | 44 961 | 962 | 964 | 965 | file://$PROJECT_DIR$/src/main/java/analyzer/collectors/HashMapCollector.java 966 | 60 967 | 968 | 970 | 971 | 973 | 974 | 975 | 976 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | 1353 | 1354 | 1355 | 1356 | 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | 1368 | 1369 | 1370 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | CS409:jar 1387 | 1388 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | No facets are configured 1400 | 1401 | 1406 | 1407 | 1408 | 1409 | 1410 | 1411 | 1412 | 1417 | 1418 | 1419 | 1420 | 1421 | 1422 | 1.8 1423 | 1424 | 1429 | 1430 | 1431 | 1432 | 1433 | 1434 | CS409 1435 | 1436 | 1442 | 1443 | 1444 | 1445 | 1446 | 1447 | 1.8 1448 | 1449 | 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | Maven: com.github.javaparser:javaparser-core:2.5.1 1460 | 1461 | 1466 | 1467 | 1468 | 1469 | 1470 | 1471 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Laurynas Sakalauskas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: Runner 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Static Code Analyzer 2 | 3 | The analyzer can detect & collect statistics and metrics about the project mentioned below: 4 | 5 | **Code smells** 6 | 7 | 1. Calculate Class Cyclomatic Complexity and limit (default: 6) 8 | 9 | 2. Calculate Weighted Method Count (WMC) and limit (default: 50) 10 | 11 | 3. Maximum number of lines per class (default: 1000) 12 | 13 | 4. Maximum number of lines per method (default: 50) 14 | 15 | 5. Maximum method name length (default: 50) 16 | 17 | 6. Maximum number of parameters in method (default: 8) 18 | 19 | 7. Maximum length of field/parameter (default: 20) 20 | 21 | 8. Maximum number of fields in the class (default: 25) 22 | 23 | 9. Maximum number of methods in the class (default: 25) 24 | 25 | 10. Minimum length of field/parameter (default: 3) 26 | 27 | 11. Minimum length of method (default: 3) 28 | 29 | **Naming Conventions** 30 | 31 | 1. Boolean names must start with is (e.g. isSomething()), unless it has parameters. 32 | 33 | 2. Class must be in CamelCase. 34 | 35 | 3. Method must be in camelCase(). 36 | 37 | 4. Parameters must be in camelCase. 38 | 39 | 5. Fields must be in camelCase, constants can be in UPPER_CASE. 40 | 41 | **Metrics** 42 | 43 | 1. Total Code lines in project. 44 | 45 | 2. Total Classes in project. 46 | 47 | 3. Total Test/Abstract/Final classes. 48 | 49 | 4. Total Interfaces in project. 50 | 51 | 5. Total Overridden methods in project. 52 | 53 | 6. Total Methods in project. 54 | 55 | 7. Total Public/private/protected methods in project. 56 | 57 | 8. Total Methods without Javadoc 58 | 59 | 9. Total Parameters in project. 60 | 61 | 10. Total Fields in project. 62 | 63 | # Building the app 64 | 65 | 1. Install Maven dependencies: `mvn install` 66 | 2. Compile java file: `javac src/main/java/Runner.java` 67 | 2. Run java file `java src/main/java/Runner` 68 | 69 | ## Outline of design 70 | 71 | I have used JavaParser framework’s *VoidVisitorAdapter* to parse AST (Abstract syntax tree). Visitor pattern is being used heavily to walk through the AST and collect all the relevant statistics and warnings. There are 7 different visitor classes separated so they handle different responsibilities to make the code more maintainable, understandable and with SOLID principles in mind. 72 | 73 | All the metrics and errors are collected in *Collector* object which holds all the data collected and can be reused throughout the project. *Collector* is an interface and I have added an implementation of *HashMapCollector* to simply collect the data into *HashMap*. In the future, if we for example want to store data to MySQL database, we can add *MysqlCollector* implementation and we would only need to change one line of code where we set the implementation to use. 74 | 75 | I have separated different console printers to the individual classes as well, (just in case we want to skip printing some stats) namely: *ComplexityPrinter* which prints statistics about Class Cyclomatic Complexity and Weighted Method Counts. *MetricPrinter* which prints all the available metrics about the java project and finally a *WarningPrinter* which prints out all the warnings produced by running the analyzer. 76 | 77 | With the current design it is easy to adjust the project how we want to output statistics and what else we may want analyse without modifying any collector code. Therefore, if we would like to display the results in HTML file, we can just add new Printers with some other output strategy. However, since the assignment is purely based on content, not on the style, I decided to simply output the results into console, rather than producing a nice HTML report with some graphs. 78 | 79 | There is a *Config.java* class where all the constants are defined in one file for easy configuration change. This could be useful to adjust the limits or enable/disable some parts of the application without going and searching through the code. 80 | 81 | ## Results and evaluation 82 | The analyzer application produces the relevant results for any Java project you put in. As a test cases, I have included several java projects from github (that produced correct metrics and did not generate any false warnings). 83 | -------------------------------------------------------------------------------- /Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakalauskas/Java-Static-Analyzer/2fe7c225f6a878cff566129e3b31205e44568ec0/Report.pdf -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cs409 8 | 1 9 | 0.1-FINAL 10 | 11 | 12 | 13 | 14 | com.github.javaparser 15 | javaparser-core 16 | 3.4.3 17 | 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-compiler-plugin 24 | 25 | 1.8 26 | 1.8 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/Runner.java: -------------------------------------------------------------------------------- 1 | import analyzer.Analyzer; 2 | import analyzer.collectors.HashMapCollector; 3 | import analyzer.printers.ComplexityPrinter; 4 | import analyzer.printers.MetricPrinter; 5 | import analyzer.printers.WarningPrinter; 6 | 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | import java.util.Scanner; 12 | 13 | /** 14 | * Created by laurynassakalauskas on 15/10/2016. 15 | */ 16 | public class Runner { 17 | 18 | 19 | /** 20 | * Simple Analyzer runner method 21 | * 22 | * @param args 23 | * @throws Exception 24 | */ 25 | public static void main(String[] args) throws Exception { 26 | 27 | Scanner in = new Scanner(System.in); 28 | 29 | System.out.println("Please type full path to project directory you want to analyze."); 30 | 31 | String tempPath = in.nextLine(); 32 | // String tempPath = "/Users/laurynassakalauskas/IdeaProjects/CS409/testcases/java-design-patterns"; 33 | try { 34 | run(tempPath); 35 | } catch (Exception e) { 36 | System.out.println("Looks like you specified not existing directory. Exiting..."); 37 | } 38 | 39 | 40 | // other way of using 41 | // if (args.length == 0) { 42 | // System.out.println("Usage: java -jar analyzer [path]"); 43 | // } else { 44 | // String path = args[0]; 45 | // 46 | // run(path); 47 | // } 48 | 49 | } 50 | 51 | /** 52 | * Convert String to path if needed 53 | * 54 | * @param path 55 | */ 56 | private static void run(String path) { 57 | run(Paths.get(path)); 58 | } 59 | 60 | /** 61 | * Walk through the files and collect stats and then print them out 62 | * 63 | * @param p 64 | */ 65 | private static void run(Path p) { 66 | try { 67 | HashMapCollector collector = new HashMapCollector(); 68 | 69 | Files.walkFileTree(p, new Analyzer(collector)); 70 | 71 | new WarningPrinter(collector).print(); 72 | new MetricPrinter(collector).print(); 73 | new ComplexityPrinter(collector).print(); 74 | 75 | } catch (IOException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/analyzer/AbstractVoidVisitorAdapter.java: -------------------------------------------------------------------------------- 1 | package analyzer; 2 | 3 | import com.github.javaparser.ast.CompilationUnit; 4 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 5 | 6 | /** 7 | * Wrapper class for automatically resolving classname and reusing throughout the visitor 8 | * 9 | * Created by laurynassakalauskas on 15/10/2016. 10 | */ 11 | public abstract class AbstractVoidVisitorAdapter extends VoidVisitorAdapter { 12 | 13 | protected String className; 14 | 15 | @Override 16 | public void visit(CompilationUnit cu, A arg) { 17 | 18 | if (cu.getTypes().size() > 0) { 19 | className = cu.getTypes().get(0).getNameAsString(); 20 | } else { 21 | className = "Unknown class"; 22 | } 23 | 24 | 25 | super.visit(cu, arg); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/analyzer/Analyzer.java: -------------------------------------------------------------------------------- 1 | package analyzer; 2 | 3 | import analyzer.collectors.Collector; 4 | import analyzer.visitors.*; 5 | import com.github.javaparser.JavaParser; 6 | import com.github.javaparser.ParseException; 7 | import com.github.javaparser.ast.CompilationUnit; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.FileVisitResult; 11 | import java.nio.file.Path; 12 | import java.nio.file.SimpleFileVisitor; 13 | import java.nio.file.attribute.BasicFileAttributes; 14 | 15 | /** 16 | * Created by laurynassakalauskas on 15/10/2016. 17 | */ 18 | public class Analyzer extends SimpleFileVisitor { 19 | 20 | private Collector collector; 21 | 22 | public Analyzer(Collector collector) { 23 | 24 | this.collector = collector; 25 | } 26 | 27 | @Override 28 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 29 | throws IOException { 30 | 31 | if (isNotJava(file)) 32 | return FileVisitResult.CONTINUE; 33 | 34 | // initialize collector 35 | ComplexityCounter complexityCounter = new ComplexityCounter(getClassName(file), collector); 36 | 37 | // initialize compilation unit 38 | CompilationUnit unit = JavaParser.parse(file.toFile()); 39 | 40 | // collect all the stats 41 | new BooleanMethodVisitor().visit(unit, collector); 42 | new TooLongVisitor().visit(unit, collector); 43 | new TooSmallVisitor().visit(unit, collector); 44 | new VariableNamingConventionVisitor().visit(unit, collector); 45 | new ClassLineCounterVisitor().visit(unit, collector); 46 | new ClassComplexityVisitor().visit(unit, complexityCounter); 47 | new MetricVisitor().visit(unit, collector); 48 | 49 | // print analysis 50 | complexityCounter.analyze(); 51 | collector.addComplexityResults(getClassName(file), complexityCounter); 52 | 53 | return FileVisitResult.CONTINUE; 54 | } 55 | 56 | /** 57 | * Extract class name from the path 58 | * 59 | * @param file 60 | * @return 61 | */ 62 | private String getClassName(Path file) { 63 | 64 | String filename = file.getFileName().toString(); 65 | 66 | if (filename.indexOf(".") > 0) { 67 | filename = filename.substring(0, filename.lastIndexOf(".")); 68 | } 69 | return filename; 70 | } 71 | 72 | /** 73 | * We only need to analyze the Java files 74 | * 75 | * @param file 76 | * @return 77 | */ 78 | private boolean isNotJava(Path file) { 79 | 80 | return !file.toString().endsWith("java"); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/analyzer/ComplexityCounter.java: -------------------------------------------------------------------------------- 1 | package analyzer; 2 | 3 | import analyzer.collectors.Collector; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by laurynassakalauskas on 15/10/2016. 10 | */ 11 | public class ComplexityCounter { 12 | 13 | 14 | private static final int MAX_CYCLOMATIC_COMPLEXITY = Config.MAX_CYCLOMATIC_COMPLEXITY; 15 | 16 | private static final int MAX_WEIGHTED_COUNT = Config.MAX_WEIGHTED_COUNT; 17 | 18 | private final String classname; 19 | 20 | private Collector collector; 21 | 22 | private HashMap map = new HashMap<>(); 23 | 24 | private HashMap usage = new HashMap<>(); 25 | 26 | public ComplexityCounter(String classname, Collector collector) { 27 | this.classname = classname; 28 | this.collector = collector; 29 | } 30 | 31 | 32 | /** 33 | * Collect information about each method 34 | */ 35 | public void add(String methodName, String type) { 36 | 37 | // check if such method was ever updated 38 | if (map.containsKey(methodName)) { 39 | 40 | map.put(methodName, map.get(methodName) + 1); 41 | } else { 42 | 43 | // method name was never queried. create a new HashMap 44 | map.put(methodName, 1); 45 | } 46 | 47 | // put information about different stats 48 | if (usage.containsKey(type)) { 49 | 50 | usage.put(type, usage.get(type) + 1); 51 | } else { 52 | usage.put(type, 1); 53 | } 54 | } 55 | 56 | public void analyze() { 57 | cyclomaticComplexity(); 58 | weightedMethodCount(); 59 | } 60 | 61 | /** 62 | * Retrieves total usage of IF, While, Foreach, bitwise and etc 63 | * 64 | * @return 65 | */ 66 | public HashMap getUsage() { 67 | return usage; 68 | } 69 | 70 | /** 71 | * Calculate cyclomatic complexity for all methods 72 | */ 73 | protected void cyclomaticComplexity() { 74 | for (Map.Entry entry: map.entrySet()) { 75 | 76 | cyclomaticComplexity(entry.getKey()); 77 | 78 | } 79 | } 80 | 81 | /** 82 | * Calculate cyclomatic complexity for selected method 83 | * 84 | * @param method 85 | * @return 86 | */ 87 | public int cyclomaticComplexity(String method) { 88 | 89 | int sum = map.get(method); 90 | 91 | if (sum > MAX_CYCLOMATIC_COMPLEXITY) { 92 | collector.addWarning(classname, "\"" + method + "\" method has Cyclomatic complexity of more than " + MAX_CYCLOMATIC_COMPLEXITY + ". Consider minimizing class complexity."); 93 | } 94 | 95 | return sum; 96 | } 97 | 98 | 99 | /** 100 | * Calculate total weighted method count 101 | * 102 | * @return 103 | */ 104 | public int weightedMethodCount() { 105 | 106 | int sum = 0; 107 | 108 | for (Map.Entry entry: map.entrySet()) { 109 | sum += entry.getValue(); 110 | } 111 | 112 | // check if there are any methods 113 | if (map.entrySet().size() > 0) { 114 | sum = sum / map.entrySet().size(); 115 | } 116 | 117 | if (sum > MAX_WEIGHTED_COUNT) { 118 | collector.addWarning(classname, "Class has Weighted Method Count more than " + MAX_WEIGHTED_COUNT + ". Consider minimizing class complexity."); 119 | } 120 | 121 | return sum; 122 | } 123 | 124 | 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/analyzer/Config.java: -------------------------------------------------------------------------------- 1 | package analyzer; 2 | 3 | /** 4 | * Created by laurynassakalauskas on 15/10/2016. 5 | */ 6 | public final class Config { 7 | 8 | 9 | /** 10 | * Max count for calculating "Cyclomatic Complexity" 11 | */ 12 | public static final int MAX_CYCLOMATIC_COMPLEXITY = 50; 13 | 14 | /** 15 | * Max count for calculating "Weighted Method Count (WMC)" 16 | */ 17 | public static final int MAX_WEIGHTED_COUNT = 50; 18 | 19 | /** 20 | * Maximum length of class file 21 | */ 22 | public static final int MAX_CLASS_LENGTH = 1000; 23 | 24 | /** 25 | * Maximum number of lines per method 26 | */ 27 | public static final int MAX_BODY_LENGTH = 50; 28 | 29 | /** 30 | * Maximum length per method name 31 | */ 32 | public static final int MAX_METHOD_NAME_LENGTH = 50; 33 | 34 | /** 35 | * Maximum number of parameters per method 36 | */ 37 | public static final int MAX_PARAM_COUNT = 8; 38 | 39 | /** 40 | * Maximum length of field/parameter 41 | */ 42 | public static final int MAX_VARIABLE_LENGTH = 20; 43 | 44 | /** 45 | * Maximum number of variables declared per class 46 | */ 47 | public static final int MAX_VARIABLE_COUNT = 15; 48 | 49 | /** 50 | * Maximum number of methods per class 51 | */ 52 | public static final int MAX_METHODS_COUNT = 25; 53 | 54 | /** 55 | * Minimum length of variable 56 | */ 57 | public static final int MIN_VARIABLE_LENGTH = 3; 58 | 59 | /** 60 | * Minimum length of method name 61 | */ 62 | public static final int MIN_METHOD_NAME = 3; 63 | 64 | /** 65 | * Boolean method must start with is (e.g. isSomething()) when there are no parameters 66 | */ 67 | public static final boolean BOOLEAN_STARTS_WITH_IS = true; 68 | 69 | /** 70 | * Class name must be in CamelCase 71 | */ 72 | public static final boolean CAMEL_CASE_CLASS_NAME = true; 73 | 74 | /** 75 | * Method must be in camelCase 76 | */ 77 | public static final boolean METHOD_IN_CAMEL_CASE = true; 78 | /** 79 | * Fields/parameters must be in camelCase 80 | */ 81 | public static final boolean PARAM_IN_CAMEL_CASE = true; 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/analyzer/collectors/Collector.java: -------------------------------------------------------------------------------- 1 | package analyzer.collectors; 2 | 3 | import analyzer.ComplexityCounter; 4 | 5 | /** 6 | * Created by laurynassakalauskas on 23/10/2016. 7 | */ 8 | public interface Collector { 9 | 10 | /** 11 | * Add warnings to queue 12 | * 13 | * @param className 14 | * @param warning 15 | */ 16 | void addWarning(String className, String warning); 17 | 18 | /** 19 | * Add complexity results for the class 20 | * 21 | * @param className 22 | * @param counter 23 | */ 24 | void addComplexityResults(String className, ComplexityCounter counter); 25 | 26 | /** 27 | * Increment some metric by +1 28 | * 29 | * @param metricName 30 | */ 31 | void incrementMetric(String metricName); 32 | 33 | /** 34 | * Increment some metric by specified count 35 | * 36 | * @param metricName 37 | * @param count 38 | */ 39 | void incrementMetric(String metricName, int count); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/analyzer/collectors/HashMapCollector.java: -------------------------------------------------------------------------------- 1 | package analyzer.collectors; 2 | 3 | import analyzer.ComplexityCounter; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | /** 10 | * Collect all stats to hashmap 11 | * 12 | * Created by laurynassakalauskas on 15/10/2016. 13 | */ 14 | public class HashMapCollector implements Collector { 15 | 16 | 17 | protected static HashMap> warnings; 18 | 19 | protected static HashMap stats; 20 | 21 | protected static HashMap complexity; 22 | 23 | public static HashMap> getWarnings() { 24 | return warnings; 25 | } 26 | 27 | public static HashMap getStats() { 28 | return stats; 29 | } 30 | 31 | public static HashMap getComplexity() { 32 | return complexity; 33 | } 34 | 35 | public HashMapCollector() { 36 | warnings = new HashMap<>(); 37 | stats = new HashMap<>(); 38 | complexity = new HashMap<>(); 39 | } 40 | 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | public void addWarning(String className, String warning) { 46 | 47 | if (warnings.containsKey(className)) { 48 | warnings.get(className).add(warning); 49 | } else { 50 | 51 | warnings.put(className, new ArrayList() {{ 52 | add(warning); 53 | }}); 54 | } 55 | 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | public void addComplexityResults(String className, ComplexityCounter counter) { 62 | complexity.put(className, counter); 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | public void incrementMetric(String metricName) { 69 | if (stats.containsKey(metricName)) { 70 | stats.put(metricName, stats.get(metricName) + 1); 71 | } else { 72 | stats.put(metricName, 1); 73 | } 74 | } 75 | 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | public void incrementMetric(String metricName, int count) { 81 | if (stats.containsKey(metricName)) { 82 | stats.put(metricName, stats.get(metricName) + count); 83 | } else { 84 | stats.put(metricName, count); 85 | } 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/analyzer/printers/AbstractPrinter.java: -------------------------------------------------------------------------------- 1 | package analyzer.printers; 2 | 3 | import analyzer.collectors.HashMapCollector; 4 | 5 | /** 6 | * Created by laurynassakalauskas on 22/10/2016. 7 | */ 8 | public abstract class AbstractPrinter { 9 | 10 | protected HashMapCollector collector; 11 | 12 | public AbstractPrinter(HashMapCollector collector) { 13 | 14 | this.collector = collector; 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/analyzer/printers/ComplexityPrinter.java: -------------------------------------------------------------------------------- 1 | package analyzer.printers; 2 | 3 | import analyzer.ComplexityCounter; 4 | import analyzer.collectors.HashMapCollector; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by laurynassakalauskas on 15/10/2016. 11 | */ 12 | public class ComplexityPrinter extends AbstractPrinter implements Printable{ 13 | 14 | 15 | public ComplexityPrinter(HashMapCollector collector) { 16 | super(collector); 17 | } 18 | 19 | /** 20 | * Print out complexity stats to the console 21 | */ 22 | @Override 23 | public void print() { 24 | HashMap totalUsage = new HashMap<>(); 25 | 26 | int totalWCM = 0; 27 | 28 | for (Map.Entry entry: collector.getComplexity().entrySet()) { 29 | 30 | totalWCM += entry.getValue().weightedMethodCount(); 31 | for (Map.Entry e: entry.getValue().getUsage().entrySet()) { 32 | totalUsage.merge(e.getKey(), e.getValue(), Integer::sum); 33 | } 34 | 35 | } 36 | 37 | totalWCM /= collector.getComplexity().entrySet().size(); 38 | 39 | System.out.println("|==================================================================|"); 40 | System.out.println("|----------------------Complexity Stats----------------------------|"); 41 | System.out.println("|==================================================================|"); 42 | System.out.format("|%60s|%5s|\n", "Type", "Count"); 43 | System.out.println("|==================================================================|"); 44 | 45 | for (Map.Entry entry: totalUsage.entrySet()) { 46 | 47 | System.out.format("|%60s|%5s|\n", entry.getKey(), entry.getValue()); 48 | 49 | } 50 | System.out.println("|==================================================================|"); 51 | System.out.println("|---------------------Weighted method count------------------------|"); 52 | System.out.println("|==================================================================|"); 53 | System.out.format("|%60s|%5s|\n", "Class", "Count"); 54 | System.out.println("|==================================================================|"); 55 | for (Map.Entry entry: collector.getComplexity().entrySet()) { 56 | 57 | System.out.format("|%60s|%5s|\n", entry.getKey(), entry.getValue().weightedMethodCount()); 58 | 59 | 60 | } 61 | System.out.println("|==================================================================|"); 62 | System.out.format("|%60s|%5s|\n", "Average weighted method count: ", totalWCM); 63 | System.out.println("|==================================================================|"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/analyzer/printers/MetricPrinter.java: -------------------------------------------------------------------------------- 1 | package analyzer.printers; 2 | 3 | import analyzer.collectors.HashMapCollector; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * Created by laurynassakalauskas on 15/10/2016. 9 | */ 10 | public class MetricPrinter extends AbstractPrinter implements Printable { 11 | 12 | public MetricPrinter(HashMapCollector collector) { 13 | super(collector); 14 | } 15 | 16 | /** 17 | * Print out metrics to the console 18 | */ 19 | @Override 20 | public void print() { 21 | System.out.println("____________________________________________________________________"); 22 | System.out.println("|--------------------------METRICS---------------------------------|"); 23 | System.out.println("|==================================================================|"); 24 | System.out.format("|%60s|%5s|\n", "Type", "Count"); 25 | System.out.println("|==================================================================|"); 26 | 27 | for (Map.Entry entry: collector.getStats().entrySet()) { 28 | 29 | System.out.format("|%60s|%5s|\n", entry.getKey(), entry.getValue()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/analyzer/printers/Printable.java: -------------------------------------------------------------------------------- 1 | package analyzer.printers; 2 | 3 | /** 4 | * Created by laurynassakalauskas on 15/10/2016. 5 | */ 6 | public interface Printable { 7 | 8 | void print(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/analyzer/printers/WarningPrinter.java: -------------------------------------------------------------------------------- 1 | package analyzer.printers; 2 | 3 | import analyzer.collectors.HashMapCollector; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by laurynassakalauskas on 15/10/2016. 10 | */ 11 | public class WarningPrinter extends AbstractPrinter implements Printable { 12 | 13 | public WarningPrinter(HashMapCollector collector) { 14 | super(collector); 15 | } 16 | 17 | 18 | /** 19 | * Print out warnings to the console 20 | */ 21 | @Override 22 | public void print() { 23 | System.out.println("____________________________________________________________________"); 24 | System.out.println("|---------------------------WARNINGS-------------------------------|"); 25 | System.out.println("|==================================================================|"); 26 | 27 | if (collector.getWarnings().entrySet().size() == 0) { 28 | 29 | System.out.println("|No warnings were found. That's awesome! |"); 30 | 31 | } else { 32 | System.out.println("|Oh no, there are " + collector.getWarnings().entrySet().size()+ " warnings you need to fix |"); 33 | 34 | } 35 | System.out.println("|==================================================================|"); 36 | 37 | for (Map.Entry> entry: collector.getWarnings().entrySet()) { 38 | 39 | System.out.println(entry.getKey() + ": " + entry.getValue().size() + " warnings"); 40 | 41 | for (String warning: entry.getValue()) { 42 | 43 | System.out.println("[WARNING] " + warning); 44 | } 45 | System.out.println(); 46 | } 47 | 48 | 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/analyzer/visitors/BooleanMethodVisitor.java: -------------------------------------------------------------------------------- 1 | package analyzer.visitors; 2 | 3 | import analyzer.AbstractVoidVisitorAdapter; 4 | import analyzer.Config; 5 | import analyzer.collectors.Collector; 6 | import com.github.javaparser.ast.body.MethodDeclaration; 7 | 8 | public class BooleanMethodVisitor extends AbstractVoidVisitorAdapter { 9 | 10 | 11 | /** 12 | * Boolean method should start isSomething(), not getSomething() 13 | * 14 | * getSomething(someVar) is OK, though 15 | * 16 | * @param declaration 17 | * @param collector 18 | */ 19 | @Override 20 | public void visit(MethodDeclaration declaration, Collector collector) { 21 | 22 | if (Config.BOOLEAN_STARTS_WITH_IS && declaration.getType().toString().equals("boolean") && declaration.getParameters().size() == 0) { 23 | 24 | if (!declaration.getNameAsString().startsWith("is")) { 25 | 26 | collector.addWarning(className, "Method name \"" + declaration.getType().toString() + "\" should start with is, e.g.\"isSomething()\""); 27 | 28 | } 29 | } 30 | 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/analyzer/visitors/ClassComplexityVisitor.java: -------------------------------------------------------------------------------- 1 | package analyzer.visitors; 2 | 3 | import analyzer.AbstractVoidVisitorAdapter; 4 | import analyzer.ComplexityCounter; 5 | import com.github.javaparser.ast.body.MethodDeclaration; 6 | import com.github.javaparser.ast.stmt.*; 7 | 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | /** 12 | * Created by laurynassakalauskas on 15/10/2016. 13 | */ 14 | public class ClassComplexityVisitor extends AbstractVoidVisitorAdapter { 15 | 16 | protected String methodName; 17 | 18 | /** 19 | * Before visiting any method, mark the method name, so we could collect it later. 20 | * 21 | * @param declaration 22 | * @param counter 23 | */ 24 | public void visit(MethodDeclaration declaration, ComplexityCounter counter) { 25 | 26 | methodName = declaration.getNameAsString(); 27 | 28 | super.visit(declaration, counter); 29 | } 30 | 31 | 32 | /** 33 | * Count foreach statements 34 | * 35 | * @param statement 36 | * @param counter 37 | */ 38 | @Override 39 | public void visit(ForeachStmt statement, ComplexityCounter counter) { 40 | counter.add(methodName, "FOREACH"); 41 | 42 | super.visit(statement, counter); 43 | } 44 | 45 | /** 46 | * Count for statements 47 | * 48 | * @param statement 49 | * @param counter 50 | */ 51 | @Override 52 | public void visit(ForStmt statement, ComplexityCounter counter) { 53 | counter.add(methodName, "FOR"); 54 | 55 | super.visit(statement, counter); 56 | } 57 | 58 | /** 59 | * Count if/else statements 60 | * 61 | * @param statement 62 | * @param counter 63 | */ 64 | @Override 65 | public void visit(IfStmt statement, ComplexityCounter counter) { 66 | counter.add(methodName, "IF"); 67 | 68 | if(statement.getElseStmt() != null) { 69 | counter.add(methodName, "ELSE"); 70 | } 71 | 72 | 73 | String condition = statement.getCondition().toString(); 74 | 75 | regexCheck(condition, Pattern.compile("/(\\s|\\w|\\d)&(\\s|\\w|\\d)/xg"), "BITWISE_AND_OPERATOR", counter); 76 | regexCheck(condition, Pattern.compile("/(\\s|\\w|\\d)\\|(\\s|\\w|\\d)/xg"), "BITWISE_OR_OPERATOR", counter); 77 | regexCheck(condition, Pattern.compile("/(\\s|\\w|\\d)&&(\\s|\\w|\\d)/xg"), "AND_OPERATOR", counter); 78 | regexCheck(condition, Pattern.compile("/(\\s|\\w|\\d)\\|\\|(\\s|\\w|\\d)/xg"), "OR_OPERATOR", counter); 79 | 80 | 81 | super.visit(statement, counter); 82 | } 83 | 84 | 85 | private void regexCheck(String haystack, Pattern pattern, String type, ComplexityCounter counter) { 86 | Matcher matcher = pattern.matcher(haystack); 87 | 88 | while (matcher.find()) { 89 | counter.add(methodName, type); 90 | } 91 | } 92 | 93 | /** 94 | * Count switch statements 95 | * 96 | * @param statement 97 | * @param counter 98 | */ 99 | @Override 100 | public void visit(SwitchEntryStmt statement, ComplexityCounter counter) { 101 | 102 | 103 | for (Statement st :statement.getStatements()) { 104 | 105 | counter.add(methodName, "SWITCH"); 106 | 107 | } 108 | 109 | super.visit(statement, counter); 110 | } 111 | 112 | /** 113 | * Count throw statements 114 | * 115 | * @param statement 116 | * @param counter 117 | */ 118 | @Override 119 | public void visit(ThrowStmt statement, ComplexityCounter counter) { 120 | 121 | counter.add(methodName, "THROW"); 122 | 123 | super.visit(statement, counter); 124 | } 125 | 126 | /** 127 | * Count try statements 128 | * 129 | * @param statement 130 | * @param counter 131 | */ 132 | @Override 133 | public void visit(TryStmt statement, ComplexityCounter counter) { 134 | 135 | counter.add(methodName, "TRY"); 136 | 137 | super.visit(statement, counter); 138 | } 139 | 140 | /** 141 | * Count catch statements 142 | * 143 | * @param statement 144 | * @param counter 145 | */ 146 | @Override 147 | public void visit(CatchClause statement, ComplexityCounter counter) { 148 | 149 | counter.add(methodName, "CATCH"); 150 | 151 | super.visit(statement, counter); 152 | } 153 | 154 | /** 155 | * Count while statements 156 | * 157 | * @param statement 158 | * @param counter 159 | */ 160 | @Override 161 | public void visit(WhileStmt statement, ComplexityCounter counter) { 162 | 163 | counter.add(methodName, "WHILE"); 164 | 165 | super.visit(statement, counter); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/analyzer/visitors/ClassLineCounterVisitor.java: -------------------------------------------------------------------------------- 1 | package analyzer.visitors; 2 | 3 | import analyzer.AbstractVoidVisitorAdapter; 4 | import analyzer.Config; 5 | import analyzer.collectors.Collector; 6 | import com.github.javaparser.ast.CompilationUnit; 7 | 8 | /** 9 | * Created by laurynassakalauskas on 15/10/2016. 10 | */ 11 | public class ClassLineCounterVisitor extends AbstractVoidVisitorAdapter { 12 | 13 | private static final int MAX_CLASS_LENGTH = Config.MAX_CLASS_LENGTH; 14 | 15 | /** 16 | * Calculate number of lines in the class 17 | * 18 | */ 19 | public void visit(CompilationUnit cu, Collector collector) { 20 | 21 | super.visit(cu, collector); 22 | 23 | int count = cu.toString().split("\n").length; 24 | 25 | if (count > MAX_CLASS_LENGTH) { 26 | collector.addWarning(className, "Class has more than " + MAX_CLASS_LENGTH + " lines"); 27 | } 28 | 29 | collector.incrementMetric("Code Lines", count); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/analyzer/visitors/MetricVisitor.java: -------------------------------------------------------------------------------- 1 | package analyzer.visitors; 2 | 3 | import analyzer.AbstractVoidVisitorAdapter; 4 | import analyzer.collectors.Collector; 5 | import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; 6 | import com.github.javaparser.ast.body.MethodDeclaration; 7 | import com.github.javaparser.ast.expr.AnnotationExpr; 8 | import com.github.javaparser.ast.expr.VariableDeclarationExpr; 9 | 10 | public class MetricVisitor extends AbstractVoidVisitorAdapter { 11 | 12 | 13 | /** 14 | * Increment classes metric 15 | * 16 | * @param declaration 17 | * @param collector 18 | */ 19 | @Override 20 | public void visit(ClassOrInterfaceDeclaration declaration, Collector collector) { 21 | 22 | if (declaration.isInterface()) { 23 | collector.incrementMetric("Interfaces"); 24 | } else { 25 | collector.incrementMetric("Classes"); 26 | 27 | 28 | if (declaration.isFinal()) { 29 | collector.incrementMetric("Final Classes"); 30 | } 31 | if (declaration.isAbstract()) { 32 | collector.incrementMetric("Abstract Classes"); 33 | } 34 | 35 | } 36 | 37 | if (className.endsWith("Test")) { 38 | collector.incrementMetric("Test Classes"); 39 | 40 | } 41 | 42 | 43 | 44 | super.visit(declaration, collector); 45 | } 46 | 47 | 48 | /** 49 | * Increment method and parameter count 50 | * 51 | * @param declaration 52 | * @param collector 53 | */ 54 | @Override 55 | public void visit(MethodDeclaration declaration, Collector collector) { 56 | 57 | for (AnnotationExpr annotation: declaration.getAnnotations()) { 58 | if (annotation.getName().equals("Override")) 59 | collector.incrementMetric("Overridden Methods"); 60 | 61 | } 62 | 63 | if (declaration.toString().startsWith("public")) { 64 | collector.incrementMetric("Public Methods"); 65 | } 66 | 67 | if (declaration.toString().startsWith("private")) { 68 | collector.incrementMetric("Private Methods"); 69 | } 70 | 71 | if (declaration.toString().startsWith("protected")) { 72 | collector.incrementMetric("Protected Methods"); 73 | } 74 | 75 | if (!declaration.hasComment()) { 76 | collector.incrementMetric("Methods without Javadoc"); 77 | } 78 | 79 | collector.incrementMetric("Methods"); 80 | 81 | collector.incrementMetric("Parameters", declaration.getParameters().size()); 82 | 83 | super.visit(declaration, collector); 84 | 85 | } 86 | 87 | 88 | /** 89 | * Increment field count 90 | * 91 | * @param declaration 92 | * @param collector 93 | */ 94 | @Override 95 | public void visit(VariableDeclarationExpr declaration, Collector collector) { 96 | 97 | collector.incrementMetric("Fields", declaration.getVariables().size()); 98 | 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/analyzer/visitors/TooLongVisitor.java: -------------------------------------------------------------------------------- 1 | package analyzer.visitors; 2 | 3 | import analyzer.AbstractVoidVisitorAdapter; 4 | import analyzer.Config; 5 | import analyzer.collectors.Collector; 6 | import com.github.javaparser.ast.body.MethodDeclaration; 7 | import com.github.javaparser.ast.body.Parameter; 8 | import com.github.javaparser.ast.body.VariableDeclarator; 9 | import com.github.javaparser.ast.expr.MethodCallExpr; 10 | import com.github.javaparser.ast.expr.VariableDeclarationExpr; 11 | 12 | public class TooLongVisitor extends AbstractVoidVisitorAdapter { 13 | 14 | 15 | public static final int MAX_BODY_LENGTH = Config.MAX_BODY_LENGTH; 16 | 17 | private static final int MAX_METHOD_NAME_LENGTH = Config.MAX_METHOD_NAME_LENGTH; 18 | 19 | private static final int MAX_PARAM_COUNT = Config.MAX_PARAM_COUNT; 20 | 21 | private static final int MAX_VARIABLE_LENGTH = Config.MAX_VARIABLE_LENGTH; 22 | 23 | private static final int MAX_VARIABLE_COUNT = Config.MAX_VARIABLE_COUNT; 24 | 25 | private static final int MAX_METHODS_COUNT = Config.MAX_METHODS_COUNT; 26 | 27 | 28 | 29 | /** 30 | * Check for method count 31 | * 32 | * @param declaration 33 | * @param collector 34 | */ 35 | @Override 36 | public void visit(MethodCallExpr declaration, Collector collector) { 37 | 38 | if (declaration.getArguments().size() > MAX_METHODS_COUNT) { 39 | 40 | collector.addWarning(className, "Class has more than " + MAX_METHODS_COUNT + " methods"); 41 | 42 | } 43 | 44 | super.visit(declaration, collector); 45 | } 46 | 47 | /** 48 | * Check for too long method names, body, arguments 49 | * 50 | * @param declaration 51 | * @param collector 52 | */ 53 | @Override 54 | public void visit(MethodDeclaration declaration, Collector collector) { 55 | int methodBodyLength = declaration.getRange().map(range -> range.begin.line - range.end.line).orElse(0); 56 | 57 | int methodNameLength = declaration.getNameAsString().length(); 58 | 59 | int parametersCount = declaration.getParameters().size(); 60 | 61 | 62 | if (methodBodyLength > MAX_BODY_LENGTH) { 63 | collector.addWarning(className, "Method \""+ declaration.getName() +"\" body has more than " + MAX_BODY_LENGTH + " lines"); 64 | } 65 | 66 | if (methodNameLength > MAX_METHOD_NAME_LENGTH) { 67 | collector.addWarning(className, "Method \"" + declaration.getName() + "\" name is too long, it has more than " + MAX_METHOD_NAME_LENGTH + " characters"); 68 | } 69 | 70 | if (parametersCount > MAX_PARAM_COUNT) { 71 | collector.addWarning(className, "Method \"" + declaration.getName() + "\" has more than " + MAX_METHOD_NAME_LENGTH + " parameters"); 72 | } 73 | 74 | for (Parameter param: declaration.getParameters()) { 75 | 76 | if (param.getNameAsString().length() > MAX_VARIABLE_LENGTH) { 77 | 78 | collector.addWarning(className, "Method \"" + declaration.getName() + "\" variable \"" + param.getName() +"\" is way too long!"); 79 | 80 | } 81 | 82 | } 83 | 84 | } 85 | 86 | 87 | /** 88 | * Check for too many variables & for too long ones 89 | * 90 | * @param declaration 91 | * @param collector 92 | */ 93 | @Override 94 | public void visit(VariableDeclarationExpr declaration, Collector collector) { 95 | 96 | if (declaration.getVariables().size() > MAX_VARIABLE_COUNT) { 97 | collector.addWarning(className, "Class has more than " + MAX_VARIABLE_COUNT + " variables"); 98 | } 99 | 100 | for (VariableDeclarator variable: declaration.getVariables()) { 101 | 102 | if (variable.getNameAsString().length() > MAX_VARIABLE_LENGTH) { 103 | 104 | collector.addWarning(className, "Field variable \"" + variable.getNameAsString() +"\" is way too long!"); 105 | 106 | } 107 | 108 | } 109 | 110 | 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/analyzer/visitors/TooSmallVisitor.java: -------------------------------------------------------------------------------- 1 | package analyzer.visitors; 2 | 3 | import analyzer.AbstractVoidVisitorAdapter; 4 | import analyzer.Config; 5 | import analyzer.collectors.Collector; 6 | import com.github.javaparser.ast.body.MethodDeclaration; 7 | import com.github.javaparser.ast.body.VariableDeclarator; 8 | import com.github.javaparser.ast.expr.VariableDeclarationExpr; 9 | 10 | public class TooSmallVisitor extends AbstractVoidVisitorAdapter { 11 | 12 | 13 | 14 | private static final int MIN_VARIABLE_LENGTH = Config.MIN_VARIABLE_LENGTH; 15 | private static final int MIN_METHOD_NAME = Config.MIN_METHOD_NAME; 16 | 17 | 18 | /** 19 | * Check for too short method names 20 | * 21 | * @param declaration 22 | * @param collector 23 | */ 24 | @Override 25 | public void visit(MethodDeclaration declaration, Collector collector) { 26 | 27 | 28 | if (declaration.getName().toString().length() < MIN_METHOD_NAME) 29 | 30 | super.visit(declaration, collector); 31 | 32 | } 33 | 34 | 35 | /** 36 | * Check for too short variable names 37 | * 38 | * @param declaration 39 | * @param collector 40 | */ 41 | @Override 42 | public void visit(VariableDeclarationExpr declaration, Collector collector) { 43 | 44 | for (VariableDeclarator variable: declaration.getVariables()) { 45 | 46 | if (variable.getNameAsString().length() < MIN_VARIABLE_LENGTH) { 47 | collector.addWarning(className, "Variable \"" + variable.getNameAsString() + "\" length is too small"); 48 | } 49 | 50 | } 51 | 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/analyzer/visitors/VariableNamingConventionVisitor.java: -------------------------------------------------------------------------------- 1 | package analyzer.visitors; 2 | 3 | import analyzer.AbstractVoidVisitorAdapter; 4 | import analyzer.Config; 5 | import analyzer.collectors.Collector; 6 | import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; 7 | import com.github.javaparser.ast.body.MethodDeclaration; 8 | import com.github.javaparser.ast.body.Parameter; 9 | import com.github.javaparser.ast.body.VariableDeclarator; 10 | import com.github.javaparser.ast.expr.VariableDeclarationExpr; 11 | 12 | public class VariableNamingConventionVisitor extends AbstractVoidVisitorAdapter { 13 | 14 | 15 | public static final String CLASS_NAME_REGEX = "([A-Z][a-z0-9]+)+"; 16 | 17 | public static final String OK_REGEX = "/(([A-Z]+_[A-Z]*)+)/gx"; 18 | 19 | protected String methodName; 20 | 21 | /** 22 | * Check if class is in camel case 23 | * 24 | * @param declaration 25 | * @param collector 26 | */ 27 | @Override 28 | public void visit(ClassOrInterfaceDeclaration declaration, Collector collector) { 29 | 30 | 31 | if (Config.CAMEL_CASE_CLASS_NAME && declaration.getNameAsString().length() > 2 && !declaration.getNameAsString().matches(CLASS_NAME_REGEX)) { 32 | 33 | collector.addWarning(className, "Class name must be in CamelCase"); 34 | 35 | } 36 | 37 | super.visit(declaration, collector); 38 | } 39 | 40 | 41 | /** 42 | * Check if method & method variables is in camelCase, not in underscore_case, 43 | * since in Java we use camelCase naming convention 44 | * 45 | * @param declaration 46 | * @param collector 47 | */ 48 | @Override 49 | public void visit(MethodDeclaration declaration, Collector collector) { 50 | 51 | methodName = declaration.getNameAsString(); 52 | 53 | if (Config.METHOD_IN_CAMEL_CASE) { 54 | if (methodName.contains("_")) { 55 | collector.addWarning(className, "Method \"" + methodName + "\" should be in 'camelCase', not in 'underscore_case'"); 56 | 57 | } else if (!methodName.matches("^[a-z][a-zA-Z0-9]*$")) { 58 | collector.addWarning(className, "Method \"" + methodName + "\" should be in 'camelCase', not in 'underscore_case'"); 59 | } 60 | } 61 | 62 | if (Config.PARAM_IN_CAMEL_CASE) { 63 | for (Parameter param: declaration.getParameters()) { 64 | 65 | if (param.getNameAsString().contains("_")) { 66 | 67 | collector.addWarning(className, "Method \"" + methodName + "\" variable \"" + param.getName() +"\" should be in 'camelCase', not in 'underscore_case'"); 68 | 69 | } 70 | 71 | } 72 | } 73 | 74 | 75 | super.visit(declaration, collector); 76 | 77 | } 78 | 79 | 80 | /** 81 | * Check if class variables is in camelCase, not in underscore_case, 82 | * since in Java we use camelCase naming convention 83 | * 84 | * @param declaration 85 | * @param collector 86 | */ 87 | @Override 88 | public void visit(VariableDeclarationExpr declaration, Collector collector) { 89 | 90 | if (Config.PARAM_IN_CAMEL_CASE) { 91 | for (VariableDeclarator variable: declaration.getVariables()) { 92 | 93 | String name = variable.getNameAsString(); 94 | 95 | if (name.matches(OK_REGEX)) // e.x. SOME_VARIABLE is OKAY 96 | continue; 97 | 98 | 99 | if (name.contains("_")) { 100 | 101 | collector.addWarning(className, "Method \"" + methodName + "\" variable \"" + name +"\" should be in 'camelCase', not in 'underscore_case'"); 102 | 103 | } 104 | 105 | } 106 | } 107 | 108 | } 109 | 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/resources/Test.java: -------------------------------------------------------------------------------- 1 | import com.github.javaparser.JavaParser; 2 | import com.github.javaparser.ast.CompilationUnit; 3 | import com.github.javaparser.ast.body.MethodDeclaration; 4 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 5 | 6 | import java.io.FileInputStream; 7 | 8 | /** 9 | * Created by laurynassakalauskas on 07/10/2016. 10 | */ 11 | public class Test { 12 | 13 | public static void main(String[] args) throws Exception { 14 | // creates an input stream for the file to be parsed 15 | FileInputStream in = new FileInputStream("Test.java"); 16 | 17 | CompilationUnit cu; 18 | try { 19 | // parse the file 20 | cu = JavaParser.parse(in); 21 | } finally { 22 | in.close(); 23 | } 24 | 25 | // visit and print the methods names 26 | new MethodVisitor().visit(cu, null); 27 | } 28 | 29 | /** 30 | * Simple visitor implementation for visiting MethodDeclaration nodes. 31 | */ 32 | private static class MethodVisitor extends VoidVisitorAdapter { 33 | 34 | @Override 35 | public void visit(MethodDeclaration n, Object arg) { 36 | // here you can access the attributes of the method. 37 | // this method will be called for all methods in this 38 | // CompilationUnit, including inner class methods 39 | System.out.println(n.getName()); 40 | super.visit(n, arg); 41 | } 42 | } 43 | } 44 | --------------------------------------------------------------------------------