From 09fa6abe4d15e5d5630367c0c8f8d21b961d2112 Mon Sep 17 00:00:00 2001 From: Mohammed Hashim Date: Tue, 18 Mar 2025 22:54:18 -0300 Subject: [PATCH 1/6] chore: Improve SRP and extract classes/methods for better modularity --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 4 +- .idea/.gitignore | 8 + .idea/aws.xml | 17 ++ .idea/compiler.xml | 13 ++ .idea/encodings.xml | 7 + .idea/inspectionProfiles/Project_Default.xml | 8 + .idea/intellij-javadocs-4.0.1.xml | 204 ++++++++++++++++++ .idea/jarRepositories.xml | 20 ++ .idea/misc.xml | 11 + .idea/vcs.xml | 6 + .../ArgumentParser/ArgumentParser.java | 32 +++ .../ArgumentParser/CLIArgumentParser.java | 41 ++++ .../{ => ArgumentParser}/InputArgs.java | 2 +- .../ArgumentParser/RegularArgumentParser.java | 30 +++ src/Designite/Designite.java | 158 ++++---------- src/Designite/SourceModel/SM_Package.java | 2 +- src/Designite/SourceModel/SM_Project.java | 94 ++++---- src/Designite/SourceModel/SM_Type.java | 2 +- src/Designite/SourceModel/TypeVisitor.java | 2 +- src/Designite/smells/ThresholdsParser.java | 44 ++-- .../ImplementationSmellDetector.java | 4 +- src/Designite/utils/CSVUtils.java | 8 +- src/Designite/utils/DJLogger.java | 59 +++++ src/Designite/utils/FileManager.java | 125 +++++++++++ src/Designite/utils/FileReader.java | 53 ----- src/Designite/utils/Logger.java | 29 --- .../DesigniteTests/DesigniteTests.java | 94 ++++---- .../DesigniteTests/InputArgsTest.java | 4 +- .../DesigniteTests/SM_FieldTest.java | 2 +- .../DesigniteTests/SM_LocalVarTests.java | 2 +- .../DesigniteTests/SM_MethodTest.java | 2 +- .../SM_Method_CalledMethodsTests.java | 4 +- .../DesigniteTests/SM_PackageTest.java | 2 +- .../DesigniteTests/SM_ParameterTest.java | 2 +- .../DesigniteTests/SM_ProjectTest.java | 4 +- .../DesigniteTests/SM_TypeTest.java | 2 +- .../metrics/MethodMetricsTest.java | 2 +- .../metrics/TypeMetricsTest.java | 2 +- 39 files changed, 760 insertions(+), 345 deletions(-) create mode 100644 .DS_Store create mode 100644 .idea/.gitignore create mode 100644 .idea/aws.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/intellij-javadocs-4.0.1.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 src/Designite/ArgumentParser/ArgumentParser.java create mode 100644 src/Designite/ArgumentParser/CLIArgumentParser.java rename src/Designite/{ => ArgumentParser}/InputArgs.java (98%) create mode 100644 src/Designite/ArgumentParser/RegularArgumentParser.java create mode 100644 src/Designite/utils/DJLogger.java create mode 100644 src/Designite/utils/FileManager.java delete mode 100644 src/Designite/utils/FileReader.java delete mode 100644 src/Designite/utils/Logger.java diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..31db69803e29f30ca47b205ce63f03e06eba7550 GIT binary patch literal 6148 zcmeHK%}T>S5Z-NTe<(r@3Oz1(Em*a!h?fxS3mDOZN=-=7V9b^#HHT8jSzpK}@p+ut z-GIS>HxWAnyWi~m>}Ed5{xHV4vkVRxvlwG-Xowt@3PE$Ft7d``xte3iQa?-PekA>h ziTfEXYK zHje>w7PPyYS2|Tp3=ji9Fo64mfQD!r%r&a513J7uqrZWO0y@4W5QRb8V6G87AY7*c z>Qru?7+j}=U6?rAV6IW8Gp<&KdCbbiO8^XTAL%Kl_6yV@&Ni59 V#97d;(gEorpa`Lk82AMSJ^`EGOq~D# literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index 0dbc612..2beafd4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ */*/*.csv csv/ +/output + tempDesigniteDebugLog*.txt tempDesigniteLog*.txt designCodeSmells.txt @@ -22,4 +24,4 @@ designCodeSmells.txt hs_err_pid* /bin/ /target/ -target/ \ No newline at end of file +target/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/aws.xml b/.idea/aws.xml new file mode 100644 index 0000000..1d6b5c3 --- /dev/null +++ b/.idea/aws.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..5457c84 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..49065d5 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..833f2e1 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/intellij-javadocs-4.0.1.xml b/.idea/intellij-javadocs-4.0.1.xml new file mode 100644 index 0000000..e296157 --- /dev/null +++ b/.idea/intellij-javadocs-4.0.1.xml @@ -0,0 +1,204 @@ + + + + + UPDATE + false + true + + TYPE + FIELD + METHOD + + + DEFAULT + PUBLIC + PROTECTED + + + + + + ^.*(public|protected|private)*.+interface\s+\w+.* + /**\n + * The interface ${name}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> + */ + + + ^.*(public|protected|private)*.+enum\s+\w+.* + /**\n + * The enum ${name}.\n + */ + + + ^.*(public|protected|private)*.+class\s+\w+.* + /**\n + * The type ${name}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> + */ + + + .+ + /**\n + * The type ${name}.\n + */ + + + + + .+ + /**\n + * Instantiates a new ${name}.\n +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + + + ^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+ + /**\n + * Gets ${partName}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${partName}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + ^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+ + /**\n + * Sets ${partName}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${partName}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + ^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+ + /**\n + * The entry point of application.\n + + <#if element.parameterList.parameters?has_content> + *\n +</#if> + * @param ${element.parameterList.parameters[0].name} the input arguments\n +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + .+ + /**\n + * ${name}<#if isNotVoid> ${return}</#if>.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${return}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + + + ^.*(public|protected|private)*.+static.*(\w\s\w)+.+ + /**\n + * The constant ${element.getName()}.\n + */ + + + ^.*(public|protected|private)*.*(\w\s\w)+.+ + /**\n + <#if element.parent.isInterface()> + * The constant ${element.getName()}.\n +<#else> + * The ${name}.\n +</#if> */ + + + .+ + /**\n + <#if element.parent.isEnum()> + *${name} ${typeName}.\n +<#else> + * The ${name}.\n +</#if>*/ + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..692b19d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Designite/ArgumentParser/ArgumentParser.java b/src/Designite/ArgumentParser/ArgumentParser.java new file mode 100644 index 0000000..ac12b9a --- /dev/null +++ b/src/Designite/ArgumentParser/ArgumentParser.java @@ -0,0 +1,32 @@ +package Designite.ArgumentParser; + +import org.apache.commons.cli.Option; + +/** + * {@code ArgumentParser} is an abstract class to share responsibility between console and debugging application + */ +public abstract class ArgumentParser { + /** + * {@code createRequiredOption}. A method to initialise required {@link Option}. + * @param shortOpt + * @param longOpt + * @param description + * @return + */ + Option createRequiredOption(String shortOpt, String longOpt, String description) { + Option option = new Option(shortOpt, longOpt, true, description); + option.setRequired(true); + return option; + } + + /** + * {@code parseArguments} converts the appropriate {@code args} parameter from the system. + * It extracts the data from system arguments. + * @param args + * @return + */ + public abstract InputArgs parseArguments(String[] args); + + + +} diff --git a/src/Designite/ArgumentParser/CLIArgumentParser.java b/src/Designite/ArgumentParser/CLIArgumentParser.java new file mode 100644 index 0000000..11d8f0b --- /dev/null +++ b/src/Designite/ArgumentParser/CLIArgumentParser.java @@ -0,0 +1,41 @@ +package Designite.ArgumentParser; + +import Designite.utils.DJLogger; +import org.apache.commons.cli.*; + +/** + * {@code CLIArgumentParser} is a subclass of {@code ArgumentParser} that requires arguments from + * the console application in order to process the given arguments. + */ +public class CLIArgumentParser extends ArgumentParser { + + @Override + public InputArgs parseArguments(String[] args) { + Options argOptions = new Options(); + argOptions.addOption(this.createRequiredOption("i", "Input", "Input source folder path")); + argOptions.addOption(this.createRequiredOption("o", "Output", "Path to the output folder")); + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + CommandLine cmd = null; + try { + cmd = parser.parse(argOptions, args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + formatter.printHelp("Designite", argOptions); + DJLogger.log("Quitting.."); + System.exit(1); + } + String inputFolderPath = cmd.getOptionValue("Input"); + String outputFolderPath = cmd.getOptionValue("Output"); + try { + return new InputArgs(inputFolderPath, outputFolderPath); + } catch (IllegalArgumentException ex) { + DJLogger.log(ex.getMessage()); + DJLogger.log("Quitting.."); + System.err.printf("The specified input path does not exist: %s%n", inputFolderPath); + System.exit(3); + } + return null; + } + +} diff --git a/src/Designite/InputArgs.java b/src/Designite/ArgumentParser/InputArgs.java similarity index 98% rename from src/Designite/InputArgs.java rename to src/Designite/ArgumentParser/InputArgs.java index ed7fd27..d1dc973 100644 --- a/src/Designite/InputArgs.java +++ b/src/Designite/ArgumentParser/InputArgs.java @@ -1,4 +1,4 @@ -package Designite; +package Designite.ArgumentParser; import java.io.File; diff --git a/src/Designite/ArgumentParser/RegularArgumentParser.java b/src/Designite/ArgumentParser/RegularArgumentParser.java new file mode 100644 index 0000000..7dc11b8 --- /dev/null +++ b/src/Designite/ArgumentParser/RegularArgumentParser.java @@ -0,0 +1,30 @@ +package Designite.ArgumentParser; + +import Designite.utils.DJLogger; + +import java.io.File; + + +/** + * {@code RegularArgumentParser} is a subclass of {@code ArgumentParser} that does not require any + * arguments. By default, it will use the current working directory (cwd) of the Java project + * to identify or analyze the smells in the current working project itself. + */ +public class RegularArgumentParser extends ArgumentParser { + + @Override + public InputArgs parseArguments(String[] args) { + String cwd = System.getProperty("user.dir"); + String inputFolderPath = String.join(File.separator, cwd, "src","Designite"); + String outputFolderPath = String.join(File.separator, cwd, "output"); + try { + return new InputArgs(inputFolderPath, outputFolderPath); + } catch (IllegalArgumentException ex) { + DJLogger.log(ex.getMessage()); + DJLogger.log("Quitting.."); + System.exit(3); + } + return null; + } + +} diff --git a/src/Designite/Designite.java b/src/Designite/Designite.java index 363564c..ada0ca6 100644 --- a/src/Designite/Designite.java +++ b/src/Designite/Designite.java @@ -1,134 +1,54 @@ package Designite; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import Designite.SourceModel.*; +import Designite.ArgumentParser.ArgumentParser; +import Designite.ArgumentParser.CLIArgumentParser; +import Designite.ArgumentParser.InputArgs; +import Designite.ArgumentParser.RegularArgumentParser; +import Designite.SourceModel.SM_Project; import Designite.utils.Constants; -import Designite.utils.Logger; +import Designite.utils.DJLogger; +import java.io.FileNotFoundException; import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Calendar; -import org.apache.commons.cli.*; /** - * * This is the start of the project */ public class Designite { - public static void main(String[] args) throws IOException { - InputArgs argsObj = parseArguments(args); - SM_Project project = new SM_Project(argsObj); - Logger.logFile = getlogFileName(argsObj); - //TODO: log the version number - project.parse(); - project.resolve(); - project.computeMetrics(); - project.detectCodeSmells(); - if (Constants.DEBUG) - writeDebugLog(argsObj, project); - Logger.log("Done."); - } - - private static void writeDebugLog(InputArgs argsObj, SM_Project project) { - PrintWriter writer = getDebugLogStream(argsObj); - project.printDebugLog(writer); - if (writer != null) - writer.close(); - } - - private static InputArgs parseArguments(String[] args) { - Options argOptions = new Options(); - - Option input = new Option("i", "Input", true, "Input source folder path"); - input.setRequired(true); - argOptions.addOption(input); - - Option output = new Option("o", "Output", true, "Path to the output folder"); - output.setRequired(true); - argOptions.addOption(output); - - CommandLineParser parser = new DefaultParser(); - HelpFormatter formatter = new HelpFormatter(); - CommandLine cmd = null; - - try { - cmd = parser.parse(argOptions, args); - } catch (ParseException e) { - System.out.println(e.getMessage()); - formatter.printHelp("Designite", argOptions); - Logger.log("Quitting.."); - System.exit(1); - } - if(cmd==null) - { - System.out.println("Couldn't parse the command line arguments."); - formatter.printHelp("Designite", argOptions); - Logger.log("Quitting.."); - System.exit(2); + public static void main(String[] args) throws FileNotFoundException { + ArgumentParser argumentParser = (Constants.DEBUG) ? new RegularArgumentParser() : new CLIArgumentParser(); + InputArgs argsObj = argumentParser.parseArguments(args); + DJLogger.getInstance().setOutputDirectory(argsObj.getOutputFolder()); + SM_Project project = new SM_Project(argsObj); + project.parse(); + project.resolve(); + project.computeMetrics(); + project.detectCodeSmells(); + writeDebugLog(argsObj, project); + DJLogger.log("Done."); + } + + private static void writeDebugLog(InputArgs argsObj, SM_Project project) { + if (Constants.DEBUG) { + PrintWriter writer = getDebugLogStream(argsObj); + project.printDebugLog(writer); + if (writer != null) writer.close(); } - - String inputFolderPath = cmd.getOptionValue("Input"); - String outputFolderPath = cmd.getOptionValue("Output"); - - InputArgs inputArgs= null; - try - { - inputArgs = new InputArgs(inputFolderPath, outputFolderPath); + } + + private static PrintWriter getDebugLogStream(InputArgs argsObj) { + PrintWriter writer = null; + if (!argsObj.getOutputFolder().equals("")) { + String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(Calendar.getInstance().getTime()); + String filename = argsObj.getOutputFolder() + "DesigniteDebugLog" + timeStamp + ".txt"; + try { + writer = new PrintWriter(filename); + } catch (FileNotFoundException ex) { + DJLogger.log(ex.getMessage()); + } } - catch(IllegalArgumentException ex) - { - Logger.log(ex.getMessage()); - Logger.log("Quitting.."); - System.exit(3); - } - return inputArgs; - } - - private static String getlogFileName(InputArgs argsObj) { - String file = null; - String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(Calendar.getInstance().getTime()); - file = argsObj.getOutputFolder() + File.separator + "DesigniteLog" + timeStamp + ".txt"; - ensureOutputFolderExists(argsObj.getOutputFolder()); - return file; - } - - private static void ensureOutputFolderExists(String outputFolder) { - if (outputFolder == null) - return; - File folder = new File(outputFolder); - - if (folder.exists() && folder.isDirectory()) - return; - - try - { - boolean isCreated = folder.mkdirs(); - if (!isCreated) - { - System.out.println("Couldn't create output folder."); - } - } - catch (Exception ex) - { - System.out.println("Couldn't create output folder. " + ex.getMessage()); - } - } - private static PrintWriter getDebugLogStream(InputArgs argsObj) { - PrintWriter writer = null; - if (!argsObj.getOutputFolder().equals("")) { - String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(Calendar.getInstance().getTime()); - String filename = argsObj.getOutputFolder() + "DesigniteDebugLog" + timeStamp + ".txt"; - try { - writer = new PrintWriter(filename); - } catch (FileNotFoundException ex) { - Logger.log(ex.getMessage()); - } - } - return writer; - } + return writer; + } } diff --git a/src/Designite/SourceModel/SM_Package.java b/src/Designite/SourceModel/SM_Package.java index eacfaa9..20a5298 100644 --- a/src/Designite/SourceModel/SM_Package.java +++ b/src/Designite/SourceModel/SM_Package.java @@ -10,7 +10,7 @@ import Designite.metrics.TypeMetricsExtractor; import org.eclipse.jdt.core.dom.CompilationUnit; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.metrics.TypeMetrics; import Designite.smells.designSmells.DesignSmellFacade; import Designite.smells.models.DesignCodeSmell; diff --git a/src/Designite/SourceModel/SM_Project.java b/src/Designite/SourceModel/SM_Project.java index 4739a03..4e752d9 100644 --- a/src/Designite/SourceModel/SM_Project.java +++ b/src/Designite/SourceModel/SM_Project.java @@ -1,23 +1,21 @@ package Designite.SourceModel; import java.io.File; -import java.io.IOException; import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; +import Designite.utils.DJLogger; +import Designite.utils.FileManager; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jface.text.Document; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.utils.CSVUtils; -import Designite.utils.Logger; import Designite.utils.models.Graph; public class SM_Project extends SM_SourceItem implements Parsable { @@ -40,6 +38,18 @@ public SM_Project(InputArgs argsObj) { setName(this.inputArgs.getProjectName()); } + public SM_Project() { + sourceFileList = new ArrayList(); + compilationUnitList = new ArrayList(); + packageList = new ArrayList(); + hierarchyGraph = new Graph(); + dependencyGraph = new Graph(); + setName(this.inputArgs.getProjectName()); + } + + + + public void setName(String name) { this.name = name; } @@ -68,7 +78,7 @@ public int getPackageCount() { // method used in tests public CompilationUnit createCU(String filePath) { - String fileToString = readFileToString(filePath); + String fileToString = FileManager.getInstance().readFileToString(filePath); int startingIndex = filePath.lastIndexOf(File.separatorChar); unitName = filePath.substring(startingIndex + 1); @@ -110,9 +120,9 @@ private void createPackageObjects() { private void checkNotNull(List list) { if (list == null) { - Logger.log("Application couldn't find any source code files in the specified path."); + DJLogger.log("Application couldn't find any source code files in the specified path."); System.exit(1); - Logger.log("Quitting.."); + DJLogger.log("Quitting.."); } } @@ -126,10 +136,9 @@ private SM_Package searchPackage(String packageName) { private void createCompilationUnits() { try { - getFileList(inputArgs.getSourceFolder()); - + sourceFileList = FileManager.getInstance().listFiles(inputArgs.getSourceFolder()); for (String file : sourceFileList) { - String fileToString = readFileToString(file); + String fileToString = FileManager.getInstance().readFileToString(file); int startingIndex = file.lastIndexOf(File.separatorChar); unitName = file.substring(startingIndex + 1); CompilationUnit unit = createAST(fileToString, unitName); @@ -174,32 +183,35 @@ private CompilationUnit createAST(final String content, String unitName) { return cu; } - private void getFileList(String path) { - File root = new File(path); - File[] list = root.listFiles(); - - if (list == null) - return; - for (File f : list) { - if (f.isDirectory()) { - getFileList(f.getAbsolutePath()); - } else { - - if (f.getName().endsWith(".java")) - sourceFileList.add(f.getAbsolutePath()); - } - } - return; - } - - private String readFileToString(String sourcePath) { - try { - return new String(Files.readAllBytes(Paths.get(sourcePath))); - } catch (IOException e) { - e.printStackTrace(); - return new String(); - } - } + // TODO : Duplicate code found in FileManager. + // VIOLATION: Single Responsibility +// private void getFileList(String path) { +// File root = new File(path); +// File[] list = root.listFiles(); +// +// if (list == null) +// return; +// for (File f : list) { +// if (f.isDirectory()) { +// getFileList(f.getAbsolutePath()); +// } else { +// +// if (f.getName().endsWith(".java")) +// sourceFileList.add(f.getAbsolutePath()); +// } +// } +// return; +// } + + // VIOLATION: Single Responsibility +// private String readFileToString(String sourcePath) { +// try { +// return new String(Files.readAllBytes(Paths.get(sourcePath))); +// } catch (IOException e) { +// e.printStackTrace(); +// return new String(); +// } +// } @Override public void printDebugLog(PrintWriter writer) { @@ -212,14 +224,14 @@ public void printDebugLog(PrintWriter writer) { @Override public void parse() { - Logger.log("Parsing the source code ..."); + DJLogger.log("Parsing the source code ..."); createCompilationUnits(); createPackageObjects(); parseAllPackages(); } public void resolve() { - Logger.log("Resolving symbols..."); + DJLogger.log("Resolving symbols..."); for (SM_Package pkg : packageList) { pkg.resolve(); } @@ -228,7 +240,7 @@ public void resolve() { } public void computeMetrics() { - Logger.log("Extracting metrics..."); + DJLogger.log("Extracting metrics..."); CSVUtils.initializeCSVDirectory(name, inputArgs.getOutputFolder()); for (SM_Package pkg : packageList) { pkg.extractTypeMetrics(); @@ -236,7 +248,7 @@ public void computeMetrics() { } public void detectCodeSmells() { - Logger.log("Extracting code smells..."); + DJLogger.log("Extracting code smells..."); for (SM_Package pkg : packageList) { pkg.extractCodeSmells(); } diff --git a/src/Designite/SourceModel/SM_Type.java b/src/Designite/SourceModel/SM_Type.java index e9a4ba6..f09af5b 100644 --- a/src/Designite/SourceModel/SM_Type.java +++ b/src/Designite/SourceModel/SM_Type.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.metrics.MethodMetrics; import Designite.smells.implementationSmells.ImplementationSmellDetector; import Designite.smells.models.ImplementationCodeSmell; diff --git a/src/Designite/SourceModel/TypeVisitor.java b/src/Designite/SourceModel/TypeVisitor.java index 9ccadf1..55a3d39 100644 --- a/src/Designite/SourceModel/TypeVisitor.java +++ b/src/Designite/SourceModel/TypeVisitor.java @@ -4,7 +4,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.TypeDeclaration; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import java.util.ArrayList; import java.util.List; diff --git a/src/Designite/smells/ThresholdsParser.java b/src/Designite/smells/ThresholdsParser.java index 1641d82..d282770 100644 --- a/src/Designite/smells/ThresholdsParser.java +++ b/src/Designite/smells/ThresholdsParser.java @@ -7,7 +7,7 @@ import java.io.IOException; import java.util.regex.Pattern; -import Designite.utils.Logger; +import Designite.utils.DJLogger; public class ThresholdsParser { @@ -32,7 +32,7 @@ public void parseThresholds() throws FileNotFoundException, IOException, Illegal private void checkFileExists() throws FileNotFoundException { if (!file.exists()) { String message = "constants.txt file not found in project folder."; - Logger.log(message); + DJLogger.log(message); throw new FileNotFoundException(message); } } @@ -45,7 +45,7 @@ private void parseLineByLine() throws IOException, IllegalArgumentException { if (isNotEmpty(line)) { if (!isWellFormatted(line)) { String message = "Line: " + line + "\nis not of the form 'someDescription' = 'someNumber'"; - Logger.log(message); + DJLogger.log(message); bufferedReader.close(); throw new IllegalArgumentException(message); } @@ -65,23 +65,27 @@ private boolean isWellFormatted(String line) { } private void setThresholdsStrategy(String key, Double value) throws IllegalArgumentException { - if (key.equals("deepHierarchy")) { - thresholds.setDeepHierarchy(value.intValue()); - } else if (key.equals("wideHierarchy")) { - thresholds.setWideHierarchy(value.intValue()); - } else if (key.equals("insufficientModularizationLargePublicInterface")) { - thresholds.setInsufficientModularizationLargePublicInterface(value.intValue()); - } else if (key.equals("insufficientModularizationLargeNumOfMethods")) { - thresholds.setInsufficientModularizationLargeNumOfMethods(value.intValue()); - } else if (key.equals("insufficientModularizationHighComplexity")) { - thresholds.setInsufficientModularizationHighComplexity(value.intValue()); - } else if (key.equals("wideHierarchy")) { - thresholds.setWideHierarchy(value.intValue()); - } else { - String message = "No such threshold: " + key; - Logger.log(message); - throw new IllegalArgumentException(message); - } + switch (key) { + case "deepHierarchy": + thresholds.setDeepHierarchy(value.intValue()); + break; + case "insufficientModularizationLargePublicInterface": + thresholds.setInsufficientModularizationLargePublicInterface(value.intValue()); + break; + case "insufficientModularizationLargeNumOfMethods": + thresholds.setInsufficientModularizationLargeNumOfMethods(value.intValue()); + break; + case "insufficientModularizationHighComplexity": + thresholds.setInsufficientModularizationHighComplexity(value.intValue()); + break; + case "wideHierarchy": + thresholds.setWideHierarchy(value.intValue()); + break; + default: + String message = "No such threshold: " + key; + DJLogger.log(message); + throw new IllegalArgumentException(message); + } } public ThresholdsDTO getThresholds() { diff --git a/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java b/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java index b70b7ba..f5524b1 100644 --- a/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java +++ b/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.regex.Pattern; +import Designite.utils.DJLogger; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CatchClause; import org.eclipse.jdt.core.dom.IfStatement; @@ -21,7 +22,6 @@ import Designite.metrics.MethodMetrics; import Designite.smells.ThresholdsDTO; import Designite.smells.models.ImplementationCodeSmell; -import Designite.utils.Logger; import Designite.visitors.MethodControlFlowVisitor; import Designite.visitors.NumberLiteralVisitor; @@ -274,7 +274,7 @@ private boolean isNotZeroOrOne(NumberLiteral singleNumberLiteral) { } } catch (NumberFormatException ex) { String logMessage = "Exception while parsing literal number (during Magic Number detection). " + ex.getMessage(); - Logger.log(logMessage); + DJLogger.log(logMessage); literalValue = 0.0; } return literalValue != 0.0 && literalValue != 1.0; diff --git a/src/Designite/utils/CSVUtils.java b/src/Designite/utils/CSVUtils.java index 40be67b..b510852 100644 --- a/src/Designite/utils/CSVUtils.java +++ b/src/Designite/utils/CSVUtils.java @@ -26,7 +26,7 @@ private static void createDirIfNotExists(File dir) { System.out.print("oops, couldn't create the directory " + dir); } catch (Exception e) { e.printStackTrace(); - Logger.log(e.getMessage()); + DJLogger.log(e.getMessage()); } } } @@ -56,7 +56,7 @@ private static void createCSVFile(String path, String header) { bufferedWriter.close(); } catch(IOException e) { e.printStackTrace(); - Logger.log(e.getMessage()); + DJLogger.log(e.getMessage()); } } @@ -69,7 +69,7 @@ public static void addToCSVFile(String path, String row) { bufferedWriter.close(); } catch(IOException e) { e.printStackTrace(); - Logger.log(e.getMessage()); + DJLogger.log(e.getMessage()); } } @@ -85,7 +85,7 @@ public static void addAllToCSVFile(String path, List collection) { bufferedWriter.close(); } catch(IOException e) { e.printStackTrace(); - Logger.log(e.getMessage()); + DJLogger.log(e.getMessage()); } } diff --git a/src/Designite/utils/DJLogger.java b/src/Designite/utils/DJLogger.java new file mode 100644 index 0000000..596b5a6 --- /dev/null +++ b/src/Designite/utils/DJLogger.java @@ -0,0 +1,59 @@ +package Designite.utils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Calendar; + + +public class DJLogger { + + // This variable must be private, making it public may expose sensitive information + private String logFile; + + private final static DJLogger DJ_LOGGER = getInstance(); + + public static DJLogger getInstance() { + if (DJ_LOGGER == null) { + return new DJLogger(); + } + return DJ_LOGGER; + } + + /** + * Ensure to set {@code outputDirectory} before program execution, + * as it creates log files in the specified + * directory. Failing to provide a valid directory may result in errors. + * @param outputDirectory + * @throws FileNotFoundException + */ + public void setOutputDirectory(String outputDirectory) throws FileNotFoundException { + String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(Calendar.getInstance().getTime()); + String logAbsolutePath = outputDirectory + File.separator + "DesigniteLog" + timeStamp + ".txt"; + FileManager.getInstance().createFileIfNotExists(logAbsolutePath); + this.logFile = logAbsolutePath; + } + + private DJLogger() { + } + + /** + * Logs the {@code logMessage} into the file. + * @param logMessage + */ + public static void log(String logMessage) { + System.out.println(logMessage); + if (DJ_LOGGER.logFile == null) { + //Commented the following line just to make the execution non-verbose + //System.out.println("Log file path has been not set. Logging not support."); + return; + } + try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(DJ_LOGGER.logFile, true), StandardCharsets.UTF_8))) { + writer.write(logMessage + "\n"); + // Redundant close -> try-catch closes the file after execution. + // writer.close(); + } catch (IOException ex) { + System.out.println("Exception during logging. " + ex.getMessage()); + } + } +} diff --git a/src/Designite/utils/FileManager.java b/src/Designite/utils/FileManager.java new file mode 100644 index 0000000..8f5d717 --- /dev/null +++ b/src/Designite/utils/FileManager.java @@ -0,0 +1,125 @@ +package Designite.utils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class FileManager { + + private static FileManager fileManager; + + /** + * The Singleton Design principle has been applied to manage one object only throughout the program execution. + * @return {@code Instance of} {@link FileManager} + */ + public static FileManager getInstance() { + if (fileManager == null) { + fileManager = new FileManager(); + } + return fileManager; + } + + private FileManager() { + } + + public FileManager(String sourcePath) { + listFiles(sourcePath); + } + + /** + * Returns list of all files from a nested folder, recursively traversing from + * the deepest level of folder tree. + * @param sourcePath + * @return + */ + public List listFiles(String sourcePath) { + List sourceFileList = new ArrayList(); + this.listFiles(sourcePath, sourceFileList); + return sourceFileList; + } + + /** + * Returns list of all files from a nested folder, recursively traversing tree from + * the deepest level of folder tree. + * @param sourcePath + * @param sourceFileList + * @return List + */ + private List listFiles(String sourcePath, List sourceFileList) { + File file; + try { + file = new File(sourcePath); + if (file.isFile() && file.getName().endsWith(".java")) { + sourceFileList.add(file.getAbsolutePath()); + } else if (file.isDirectory()) { + sourceFileList.addAll(listFilesFromFolder(file.getAbsolutePath())); + } else { + // help menu + // This block is a dead code. Moreover, It does not provide a resolution if the files are not found. + System.out.println("No file found to be analyzed."); + System.out.println("Usage instructions: "); + } + } catch (Exception e) { + e.printStackTrace(); + } + return sourceFileList; + } + + /** + * Returns List of files in the specified {@code folderPath} + * @param folderPath + * @return List + */ + public List listFilesFromFolder(String folderPath) { + File file = new File(folderPath); + File[] paths; + List fileList = new ArrayList(); + paths = file.listFiles(); + for (File path : paths) { + if (path.isFile() && path.getAbsolutePath().endsWith(".java")) { + fileList.add(path.getAbsolutePath()); + } else if (path.isDirectory()) { + fileList.addAll(listFilesFromFolder(path.getAbsolutePath())); + } + } + return fileList; + } + + + /** + * Reads all lines from the file specified in {@code sourcePath}. + * @param sourcePath + * @return + */ + public String readFileToString(String sourcePath) { + try { + return new String(Files.readAllBytes(Paths.get(sourcePath))); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + + /** + * Creates a new file at the specified path if it doesn't exist. + * @param filePath + * @return + * @throws FileNotFoundException + */ + public PrintWriter createFileIfNotExists(String filePath) throws FileNotFoundException { + File file = new File(filePath); + File parent = file.getParentFile(); + if (parent.isDirectory() && !parent.exists()) { + parent.mkdirs(); + } + return new PrintWriter(file); + } + + +} diff --git a/src/Designite/utils/FileReader.java b/src/Designite/utils/FileReader.java deleted file mode 100644 index 12c36a2..0000000 --- a/src/Designite/utils/FileReader.java +++ /dev/null @@ -1,53 +0,0 @@ -package Designite.utils; - -import java.io.File; -import java.util.ArrayList; - -public class FileReader { - private ArrayList pathList = new ArrayList(); - public FileReader(String sourcePath) { - listFiles(sourcePath); - } - - public ArrayList getPathList() { - return pathList; - } - - // keeping all java files from the given path in a list - public void listFiles(String sourcePath) { - File f = null; - - try { - f = new File(sourcePath); - - if (f.isFile() && f.getAbsolutePath().endsWith(".java")) { - pathList.add(f.getAbsolutePath()); - } else if (f.isDirectory()) { - getFilesFromFolder(f.getAbsolutePath()); - } else { - // help menu - System.out.println("No file found to be analyzed."); - System.out.println("Usage instructions: "); - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - // adding java files of a folder in the List - public void getFilesFromFolder(String folderPath) { - File f = new File(folderPath); - File[] paths; - - paths = f.listFiles(); - - for (File path : paths) { - if (path.isFile() && path.getAbsolutePath().endsWith(".java")) { - pathList.add(path.getAbsolutePath()); - } else if (path.isDirectory()) { - getFilesFromFolder(path.getAbsolutePath()); - } - } - } -} diff --git a/src/Designite/utils/Logger.java b/src/Designite/utils/Logger.java deleted file mode 100644 index 86a7d4e..0000000 --- a/src/Designite/utils/Logger.java +++ /dev/null @@ -1,29 +0,0 @@ -package Designite.utils; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; - -public class Logger { - public static String logFile = null; - - public static void log(String str) { - System.out.println(str); - if (logFile == null) { - //Commented the following line just to make the execution non-verbose - //System.out.println("Log file path has been not set. Logging not support."); - return; - } - try (Writer writer = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(logFile, true), StandardCharsets.UTF_8))) { - writer.write(str + "\n"); - writer.close(); - } catch (IOException ex) { - System.out.println("Exception during logging. " + ex.getMessage()); - } - } -} diff --git a/tests/DesigniteTests/DesigniteTests/DesigniteTests.java b/tests/DesigniteTests/DesigniteTests/DesigniteTests.java index 2a3caa4..3f7b526 100644 --- a/tests/DesigniteTests/DesigniteTests/DesigniteTests.java +++ b/tests/DesigniteTests/DesigniteTests/DesigniteTests.java @@ -6,62 +6,42 @@ import java.io.IOException; public abstract class DesigniteTests { - - protected static final String PARAMETER_TEST_INPUT_FILE_PATH = getTestingPath() + File.separator + "parameterTestInput.txt"; - protected static final String CALLED_METHOD_TEST_INPUT_FILE_PATH = getTestingPath() + File.separator + "calledMethodTestInput.txt"; - protected static final String TEST_BATCH_FILE_PATH = getTestingPath() + File.separator + "testBatchFile.txt"; - protected static final String IN_BATCH_FILE_PATH = getTestingPath() + File.separator + "inBatchFile.txt"; - protected static final String METRICS_FILE_PATH = getTestingPath() + File.separator + "metricsFile.txt"; - protected static final String CODE_SMELLS_FILE_PATH = getTestingPath() + File.separator + "codeSmellsFile.txt"; - - protected static final String PARAMETER_TEST_INPUT_FILE_CONTENT = "[Source folder]\n" - + getTestingPath() + File.separator + "test_inputs"; - protected static final String CALLED_METHOD_TEST_INPUT_FILE_CONTENT = "[Source folder]\n" - + getTestingPath() + File.separator + "test_inputs2"; - protected static final String TEST_BATCH_FILE_CONTENT = "[Source folder]\n" - + getTestingPath() + File.separator + "test_package"; - protected static final String IN_BATCH_FILE_CONTENT = "[Source folder]\n" - + System.getProperty("user.dir") + "\n\n" - + "[Output folder]\n" - + System.getProperty("user.dir") + File.separator + "temp"; - protected static final String IN_BATCH_FILE_CONTENT_SRC = "[Source folder]\n" - + System.getProperty("user.dir") - + File.separator + "src" + "\n\n" - + "[Output folder]\n" - + System.getProperty("user.dir") + File.separator + "temp"; - protected static final String IN_BATCH_FILE_CONTENT_SOURCE = "[Source folder]\n" - + System.getProperty("user.dir") - + File.separator + "source" + "\n\n" - + "[Output folder]\n" - + System.getProperty("user.dir") + File.separator + "temp"; - protected static final String METRICS_FILE_CONTENT = "[Source folder]\n" - + getTestingPath() + File.separator + "metrics" + "\n\n" - + "[Output folder]\n" - + System.getProperty("user.dir") + File.separator + "temp"; - protected static final String CODE_SMELLS_FILE_CONTENT = "[Source folder]\n" - + getTestingPath() + File.separator + "codeSmells"; - - protected static String getTestingPath() { - String path = System.getProperty("user.dir") + - File.separator + "tests" + - File.separator + "TestFiles"; - - return path; - } - - protected void createFileForArguments(String path, String content) { - try { - File file = new File(path); - if (!file.exists()) { - file.createNewFile(); - } - FileWriter fileWriter = new FileWriter(path, false); - BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); - bufferedWriter.write(content); - bufferedWriter.close(); - } catch(IOException e) { - e.printStackTrace(); - } - } + + protected static final String PARAMETER_TEST_INPUT_FILE_PATH = getTestingPath() + File.separator + "parameterTestInput.txt"; + protected static final String CALLED_METHOD_TEST_INPUT_FILE_PATH = getTestingPath() + File.separator + "calledMethodTestInput.txt"; + protected static final String TEST_BATCH_FILE_PATH = getTestingPath() + File.separator + "testBatchFile.txt"; + protected static final String IN_BATCH_FILE_PATH = getTestingPath() + File.separator + "inBatchFile.txt"; + protected static final String METRICS_FILE_PATH = getTestingPath() + File.separator + "metricsFile.txt"; + protected static final String CODE_SMELLS_FILE_PATH = getTestingPath() + File.separator + "codeSmellsFile.txt"; + + protected static final String PARAMETER_TEST_INPUT_FILE_CONTENT = "[Source folder]\n" + getTestingPath() + File.separator + "test_inputs"; + protected static final String CALLED_METHOD_TEST_INPUT_FILE_CONTENT = "[Source folder]\n" + getTestingPath() + File.separator + "test_inputs2"; + protected static final String TEST_BATCH_FILE_CONTENT = "[Source folder]\n" + getTestingPath() + File.separator + "test_package"; + protected static final String IN_BATCH_FILE_CONTENT = "[Source folder]\n" + System.getProperty("user.dir") + "\n\n" + "[Output folder]\n" + System.getProperty("user.dir") + File.separator + "temp"; + protected static final String IN_BATCH_FILE_CONTENT_SRC = "[Source folder]\n" + System.getProperty("user.dir") + File.separator + "src" + "\n\n" + "[Output folder]\n" + System.getProperty("user.dir") + File.separator + "temp"; + protected static final String IN_BATCH_FILE_CONTENT_SOURCE = "[Source folder]\n" + System.getProperty("user.dir") + File.separator + "source" + "\n\n" + "[Output folder]\n" + System.getProperty("user.dir") + File.separator + "temp"; + protected static final String METRICS_FILE_CONTENT = "[Source folder]\n" + getTestingPath() + File.separator + "metrics" + "\n\n" + "[Output folder]\n" + System.getProperty("user.dir") + File.separator + "temp"; + protected static final String CODE_SMELLS_FILE_CONTENT = "[Source folder]\n" + getTestingPath() + File.separator + "codeSmells"; + + protected static String getTestingPath() { + String path = System.getProperty("user.dir") + File.separator + "tests" + File.separator + "TestFiles"; + + return path; + } + + protected void createFileForArguments(String path, String content) { + try { + File file = new File(path); + if (!file.exists()) { + file.createNewFile(); + } + FileWriter fileWriter = new FileWriter(path, false); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); + bufferedWriter.write(content); + bufferedWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/tests/DesigniteTests/DesigniteTests/InputArgsTest.java b/tests/DesigniteTests/DesigniteTests/InputArgsTest.java index 34e5b20..3dda2bb 100644 --- a/tests/DesigniteTests/DesigniteTests/InputArgsTest.java +++ b/tests/DesigniteTests/DesigniteTests/InputArgsTest.java @@ -6,7 +6,7 @@ import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; public class InputArgsTest extends DesigniteTests { @@ -39,4 +39,4 @@ public void testInputArgs_getProjectName_src() { String currentProjectDir = new File(System.getProperty("user.dir")).getName(); assertEquals(currentProjectDir, args.getProjectName()); } -} \ No newline at end of file +} diff --git a/tests/DesigniteTests/DesigniteTests/SM_FieldTest.java b/tests/DesigniteTests/DesigniteTests/SM_FieldTest.java index 84d0f2f..27fc5da 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_FieldTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_FieldTest.java @@ -9,7 +9,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.AccessStates; import Designite.SourceModel.SM_Field; import Designite.SourceModel.SM_Package; diff --git a/tests/DesigniteTests/DesigniteTests/SM_LocalVarTests.java b/tests/DesigniteTests/DesigniteTests/SM_LocalVarTests.java index e26fce9..b42287f 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_LocalVarTests.java +++ b/tests/DesigniteTests/DesigniteTests/SM_LocalVarTests.java @@ -9,7 +9,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.SM_LocalVar; import Designite.SourceModel.SM_Method; import Designite.SourceModel.SM_Project; diff --git a/tests/DesigniteTests/DesigniteTests/SM_MethodTest.java b/tests/DesigniteTests/DesigniteTests/SM_MethodTest.java index 6df869d..d4419ad 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_MethodTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_MethodTest.java @@ -10,7 +10,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.AccessStates; import Designite.SourceModel.SM_Method; import Designite.SourceModel.SM_Package; diff --git a/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java b/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java index 224640d..25871c5 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java +++ b/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java @@ -8,7 +8,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.SM_Method; import Designite.SourceModel.SM_Package; import Designite.SourceModel.SM_Project; @@ -70,7 +70,7 @@ public void SM_Method_CalledMethods_staticMethod() { if (method.getCalledMethods().size()==1) { SM_Method calledMethod = method.getCalledMethods().get(0); - assertEquals("Logger", calledMethod.getParentType().getName()); + assertEquals("DJLogger", calledMethod.getParentType().getName()); assertEquals("log", calledMethod.getName()); } } diff --git a/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java b/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java index 3bcdec7..3978990 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java @@ -9,7 +9,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.SM_Package; import Designite.SourceModel.SM_Project; diff --git a/tests/DesigniteTests/DesigniteTests/SM_ParameterTest.java b/tests/DesigniteTests/DesigniteTests/SM_ParameterTest.java index 69cc16e..473c8d2 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_ParameterTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_ParameterTest.java @@ -8,7 +8,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.SM_Method; import Designite.SourceModel.SM_Parameter; import Designite.SourceModel.SM_Project; diff --git a/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java b/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java index d504231..394ea63 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java @@ -3,12 +3,10 @@ import static org.junit.Assert.*; import java.io.File; -import java.util.Iterator; import org.junit.Test; -import Designite.InputArgs; -import Designite.SourceModel.SM_Package; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.SM_Project; public class SM_ProjectTest extends DesigniteTests { diff --git a/tests/DesigniteTests/DesigniteTests/SM_TypeTest.java b/tests/DesigniteTests/DesigniteTests/SM_TypeTest.java index 25c7f15..3ed085b 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_TypeTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_TypeTest.java @@ -11,7 +11,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.AccessStates; import Designite.SourceModel.SM_Package; import Designite.SourceModel.SM_Project; diff --git a/tests/DesigniteTests/DesigniteTests/metrics/MethodMetricsTest.java b/tests/DesigniteTests/DesigniteTests/metrics/MethodMetricsTest.java index f7a589f..dc56a04 100644 --- a/tests/DesigniteTests/DesigniteTests/metrics/MethodMetricsTest.java +++ b/tests/DesigniteTests/DesigniteTests/metrics/MethodMetricsTest.java @@ -7,7 +7,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.SM_Method; import Designite.SourceModel.SM_Project; import Designite.SourceModel.SM_Type; diff --git a/tests/DesigniteTests/DesigniteTests/metrics/TypeMetricsTest.java b/tests/DesigniteTests/DesigniteTests/metrics/TypeMetricsTest.java index 2ca268d..0eb5698 100644 --- a/tests/DesigniteTests/DesigniteTests/metrics/TypeMetricsTest.java +++ b/tests/DesigniteTests/DesigniteTests/metrics/TypeMetricsTest.java @@ -7,7 +7,7 @@ import org.junit.Before; import org.junit.Test; -import Designite.InputArgs; +import Designite.ArgumentParser.InputArgs; import Designite.SourceModel.SM_Package; import Designite.SourceModel.SM_Project; import Designite.SourceModel.SM_Type; From f7dd2fc421797eb760301566d2be6a9a66c2c5b4 Mon Sep 17 00:00:00 2001 From: Mohammed Hashim Date: Thu, 20 Mar 2025 10:59:32 -0300 Subject: [PATCH 2/6] chore(refactor): Added CodeSmellDetectors, Introduced Variable, and Renamed Incorrect Method Name --- src/Designite/SourceModel/Resolver.java | 6 +- .../designSmells/CodeSmellDetector.java | 18 + .../designSmells/DesignSmellDetector.java | 86 ++- .../ImplementationSmellDetector.java | 626 +++++++++--------- src/Designite/utils/Constants.java | 2 +- 5 files changed, 366 insertions(+), 372 deletions(-) create mode 100644 src/Designite/smells/designSmells/CodeSmellDetector.java diff --git a/src/Designite/SourceModel/Resolver.java b/src/Designite/SourceModel/Resolver.java index 8a6340c..06c7ff8 100644 --- a/src/Designite/SourceModel/Resolver.java +++ b/src/Designite/SourceModel/Resolver.java @@ -210,7 +210,7 @@ private void inferTypeInfo(SM_Project parentProject, TypeInfo typeInfo, Type typ // ProblemReferenceBinding and consequently to MissingTypeBinding if (iType == null) { inferPrimitiveType(parentProject, typeInfo, iType); - infereParametrized(parentProject, typeInfo, iType); + inferParametrized(parentProject, typeInfo, iType); } else if (iType.isRecovered()) { // Search in the ast explicitly and assign String unresolvedTypeName = typeOfVar.toString().replace("[]", ""); // cover the Array case @@ -220,7 +220,7 @@ private void inferTypeInfo(SM_Project parentProject, TypeInfo typeInfo, Type typ } } else { inferPrimitiveType(parentProject, typeInfo, iType); - infereParametrized(parentProject, typeInfo, iType); + inferParametrized(parentProject, typeInfo, iType); } } @@ -297,7 +297,7 @@ private void inferPrimitiveType(SM_Project parentProject, TypeInfo typeInfo, ITy } } - private void infereParametrized(SM_Project parentProject, TypeInfo typeInfo, ITypeBinding iType) { + private void inferParametrized(SM_Project parentProject, TypeInfo typeInfo, ITypeBinding iType) { if (iType != null && iType.isParameterizedType()) { typeInfo.setParametrizedType(true); addNonPrimitiveParameters(parentProject, typeInfo, iType); diff --git a/src/Designite/smells/designSmells/CodeSmellDetector.java b/src/Designite/smells/designSmells/CodeSmellDetector.java new file mode 100644 index 0000000..d30a64d --- /dev/null +++ b/src/Designite/smells/designSmells/CodeSmellDetector.java @@ -0,0 +1,18 @@ +package Designite.smells.designSmells; + +import Designite.smells.models.CodeSmell; + +import java.util.ArrayList; +import java.util.List; + +public abstract class CodeSmellDetector { + + protected List smells = new ArrayList<>(); + + protected void addToSmells(T smell) { + smells.add(smell); + } + + public abstract T initializeCodeSmell(String smellName); + +} diff --git a/src/Designite/smells/designSmells/DesignSmellDetector.java b/src/Designite/smells/designSmells/DesignSmellDetector.java index c0f0f23..bbe44f4 100644 --- a/src/Designite/smells/designSmells/DesignSmellDetector.java +++ b/src/Designite/smells/designSmells/DesignSmellDetector.java @@ -1,56 +1,48 @@ package Designite.smells.designSmells; -import java.util.ArrayList; -import java.util.List; - import Designite.SourceModel.SourceItemInfo; import Designite.metrics.TypeMetrics; import Designite.smells.ThresholdsDTO; import Designite.smells.models.DesignCodeSmell; -public abstract class DesignSmellDetector { - - private List smells; - - private TypeMetrics typeMetrics; - private SourceItemInfo info; - private ThresholdsDTO thresholdsDTO; - - public DesignSmellDetector(TypeMetrics typeMetrics, SourceItemInfo info) { - this.typeMetrics = typeMetrics; - this.info = info; - - thresholdsDTO = new ThresholdsDTO(); - smells = new ArrayList<>(); - } - - abstract public List detectCodeSmells(); - - public List getSmells() { - return smells; - } - - public DesignCodeSmell initializeCodeSmell(String smellName) { - return new DesignCodeSmell(getSourceItemInfo().getProjectName() - , getSourceItemInfo().getPackageName() - , getSourceItemInfo().getTypeName() - , smellName); - } - - protected void addToSmells(DesignCodeSmell smell) { - smells.add(smell); - } - - protected TypeMetrics getTypeMetrics() { - return typeMetrics; - } - - protected SourceItemInfo getSourceItemInfo() { - return info; - } - - protected ThresholdsDTO getThresholdsDTO() { - return thresholdsDTO; - } +import java.util.ArrayList; +import java.util.List; + +public abstract class DesignSmellDetector extends CodeSmellDetector { + + + private final TypeMetrics typeMetrics; + private final SourceItemInfo info; + private final ThresholdsDTO thresholdsDTO; + + public DesignSmellDetector(TypeMetrics typeMetrics, SourceItemInfo info) { + this.typeMetrics = typeMetrics; + this.info = info; + + thresholdsDTO = new ThresholdsDTO(); + smells = new ArrayList<>(); + } + + abstract public List detectCodeSmells(); + + public List getSmells() { + return smells; + } + + public DesignCodeSmell initializeCodeSmell(String smellName) { + return new DesignCodeSmell(getSourceItemInfo().getProjectName(), getSourceItemInfo().getPackageName(), getSourceItemInfo().getTypeName(), smellName); + } + + protected TypeMetrics getTypeMetrics() { + return typeMetrics; + } + + protected SourceItemInfo getSourceItemInfo() { + return info; + } + + protected ThresholdsDTO getThresholdsDTO() { + return thresholdsDTO; + } } diff --git a/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java b/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java index f5524b1..7f5abe0 100644 --- a/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java +++ b/src/Designite/smells/implementationSmells/ImplementationSmellDetector.java @@ -1,331 +1,315 @@ package Designite.smells.implementationSmells; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -import Designite.utils.DJLogger; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.CatchClause; -import org.eclipse.jdt.core.dom.IfStatement; -import org.eclipse.jdt.core.dom.NumberLiteral; -import org.eclipse.jdt.core.dom.Statement; -import org.eclipse.jdt.core.dom.TryStatement; -import org.eclipse.jdt.core.dom.SwitchCase; -import org.eclipse.jdt.core.dom.SwitchStatement; - -import Designite.SourceModel.SM_Field; -import Designite.SourceModel.SM_LocalVar; -import Designite.SourceModel.SM_Method; -import Designite.SourceModel.SM_Parameter; -import Designite.SourceModel.SourceItemInfo; +import Designite.SourceModel.*; import Designite.metrics.MethodMetrics; import Designite.smells.ThresholdsDTO; +import Designite.smells.designSmells.CodeSmellDetector; import Designite.smells.models.ImplementationCodeSmell; +import Designite.utils.DJLogger; import Designite.visitors.MethodControlFlowVisitor; import Designite.visitors.NumberLiteralVisitor; +import org.eclipse.jdt.core.dom.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class ImplementationSmellDetector extends CodeSmellDetector { + + + private final MethodMetrics methodMetrics; + private final SourceItemInfo info; + private final ThresholdsDTO thresholdsDTO; + + + private static final String ABST_FUNC_CALL_FRM_CTOR = "Abstract Function Call From Constructor"; + private static final String COMPLEX_CONDITIONAL = "Complex Conditional"; + private static final String COMPLEX_METHOD = "Complex Method"; + private static final String EMPTY_CATCH_CLAUSE = "Empty catch clause"; + private static final String LONG_IDENTIFIER = "Long Identifier"; + private static final String LONG_METHOD = "Long Method"; + private static final String LONG_PARAMETER_LIST = "Long Parameter List"; + private static final String LONG_STATEMENT = "Long Statement"; + private static final String MAGIC_NUMBER = "Magic Number"; + private static final String MISSING_DEFAULT = "Missing default"; + + private static final String AND_OPERATOR_REGEX = "\\&\\&"; + private static final String OR_OPERATOR_REGEX = "\\|\\|"; + private static final Pattern EMPTY_BODY_PATTERN = Pattern.compile("^\\{\\s*\\}\\s*$"); + + private static final int LONG_RADIX = 16; + + public ImplementationSmellDetector(MethodMetrics methodMetrics, SourceItemInfo info) { + this.methodMetrics = methodMetrics; + this.info = info; + + thresholdsDTO = new ThresholdsDTO(); + smells = new ArrayList<>(); + } + + public List detectCodeSmells() { + detectAbstractFunctionCallFromConstructor(); + detectComplexConditional(); + detectComplexMethod(); + detectEmptyCatchBlock(); + detectLongIdentifier(); + detectLongMethod(); + detectLongParameterList(); + detectLongStatement(); + detectMagicNumber(); + detectMissingDefault(); + return smells; + } + + public List detectAbstractFunctionCallFromConstructor() { + if (hasAbstractFunctionCallFromConstructor()) { + addToSmells(initializeCodeSmell(ABST_FUNC_CALL_FRM_CTOR)); + } + return smells; + } + + private boolean hasAbstractFunctionCallFromConstructor() { + SM_Method method = methodMetrics.getMethod(); + if (method.isConstructor()) { + for (SM_Method calledMethod : method.getCalledMethods()) { + if (calledMethod.isAbstract()) { + return true; + } + } + } + return false; + } + + public List detectComplexConditional() { + hasComplexConditional(); + return smells; + } + + private void hasComplexConditional() { + MethodControlFlowVisitor visitor = new MethodControlFlowVisitor(); + methodMetrics.getMethod().getMethodDeclaration().accept(visitor); + for (IfStatement ifStatement : visitor.getIfStatements()) { + if (numOfBooleanSubExpressions(ifStatement) >= thresholdsDTO.getComplexCondition()) { + addToSmells(initializeCodeSmell(COMPLEX_CONDITIONAL)); + } + } + } + + private String getBooleanRegex() { + return AND_OPERATOR_REGEX + "|" + OR_OPERATOR_REGEX; + } + + private int numOfBooleanSubExpressions(IfStatement ifStatement) { + return ifStatement.getExpression().toString().split(getBooleanRegex()).length; + } + + public List detectComplexMethod() { + if (hasComplexMethod()) { + addToSmells(initializeCodeSmell(COMPLEX_METHOD)); + } + return smells; + } + + private boolean hasComplexMethod() { + return methodMetrics.getCyclomaticComplexity() >= thresholdsDTO.getComplexMethod(); + } + + public List detectEmptyCatchBlock() { + MethodControlFlowVisitor visitor = new MethodControlFlowVisitor(); + methodMetrics.getMethod().getMethodDeclaration().accept(visitor); + for (TryStatement tryStatement : visitor.getTryStatements()) { + for (Object catchClause : tryStatement.catchClauses()) { + if (!hasBody((CatchClause) catchClause)) { + addToSmells(initializeCodeSmell(EMPTY_CATCH_CLAUSE)); + } + } + } + return smells; + } + + public boolean hasBody(CatchClause catchClause) { + String body = catchClause.getBody().toString(); + return !EMPTY_BODY_PATTERN.matcher(body).matches(); + } + + public List detectLongIdentifier() { + if (hasLongIdentifier()) { + addToSmells(initializeCodeSmell(LONG_IDENTIFIER)); + } + return smells; + } + + private boolean hasLongIdentifier() { + return hasLongParameter() || hasLongLocalVar() || hasLongFieldAccess(); + } + + private boolean hasLongParameter() { + for (SM_Parameter parameter : methodMetrics.getMethod().getParameterList()) { + if (parameter.getName().length() >= thresholdsDTO.getLongIdentifier()) { + return true; + } + } + return false; + } + + private boolean hasLongLocalVar() { + for (SM_LocalVar var : methodMetrics.getMethod().getLocalVarList()) { + if (var.getName().length() >= thresholdsDTO.getLongIdentifier()) { + return true; + } + } + return false; + } + + private boolean hasLongFieldAccess() { + for (SM_Field field : methodMetrics.getMethod().getDirectFieldAccesses()) { + if (field.getName().length() >= thresholdsDTO.getLongIdentifier()) { + return true; + } + } + return false; + } + + public List detectLongMethod() { + if (hasLongMethod()) { + addToSmells(initializeCodeSmell(LONG_METHOD)); + } + return smells; + } + + private boolean hasLongMethod() { + return methodMetrics.getNumOfLines() >= thresholdsDTO.getLongMethod(); + } + + public List detectLongParameterList() { + if (hasLongParameterList()) { + addToSmells(initializeCodeSmell(LONG_PARAMETER_LIST)); + } + return smells; + } + + private boolean hasLongParameterList() { + return methodMetrics.getNumOfParameters() >= thresholdsDTO.getLongParameterList(); + } + + public List detectLongStatement() { + SM_Method currentMethod = methodMetrics.getMethod(); + if (currentMethod.hasBody()) { + String methodBody = currentMethod.getMethodBody(); + hasLongStatement(methodBody); + } + + return smells; + } + + private void hasLongStatement(String methodBody) { + //FIXME is there another non-hard-coded to replace the "\n" + String[] methodStatements = methodBody.split("\n"); + + for (String singleMethodStatement : methodStatements) { + singleMethodStatement = singleMethodStatement.trim().replaceAll("\\s+", " "); + if (isLongStatement(singleMethodStatement)) { + addToSmells(initializeCodeSmell(LONG_STATEMENT)); + } + } + } + + private boolean isLongStatement(String statement) { + return statement.length() > this.thresholdsDTO.getLongStatement(); + } + + public List detectMagicNumber() { + hasMagicNumbers(); + return smells; + } + + private void hasMagicNumbers() { + NumberLiteralVisitor visitor = new NumberLiteralVisitor(); + methodMetrics.getMethod().getMethodDeclaration().accept(visitor); + List literals = visitor.getNumberLiteralsExpressions(); + + if (literals.size() < 1) { + return; + } + + for (NumberLiteral singleNumberLiteral : literals) { + if (isLiteralValid(singleNumberLiteral)) { + addToSmells(initializeCodeSmell(MAGIC_NUMBER)); + } + } + } + + private boolean isLiteralValid(NumberLiteral singleNumberLiteral) { + boolean isValid = isNotZeroOrOne(singleNumberLiteral) && isNotArrayInitialization(singleNumberLiteral); + return isValid; + } + + // 0s and 1s are not considered as Magic Numbers + private boolean isNotZeroOrOne(NumberLiteral singleNumberLiteral) { + String numberToString = singleNumberLiteral.toString().toLowerCase().replaceAll("_", ""); + double literalValue; + try { + // hex case + if (numberToString.startsWith("0x")) { + literalValue = (double) (Long.parseLong(numberToString.replaceAll("0x", "").replaceAll("l", ""), LONG_RADIX)); + // long case + } else if (numberToString.endsWith("l")) { + literalValue = (double) (Long.parseLong(numberToString.replaceAll("l", ""))); + // float case + } else if (numberToString.endsWith("f")) { + literalValue = Float.parseFloat(numberToString.replaceAll("f", "")); + } + // double case + else { + literalValue = Double.parseDouble(numberToString); + } + } catch (NumberFormatException ex) { + String logMessage = "Exception while parsing literal number (during Magic Number detection). " + ex.getMessage(); + DJLogger.log(logMessage); + literalValue = 0.0; + } + return literalValue != 0.0 && literalValue != 1.0; + } + + + // Literals in array initializations (such as int[] arr = {0,1};) are not considered as Magic Numbers + private boolean isNotArrayInitialization(NumberLiteral singleNumberLiteral) { + return singleNumberLiteral.getParent().getNodeType() != ASTNode.ARRAY_INITIALIZER; + } + + public List detectMissingDefault() { + hasMissingDefaults(); + return smells; + } + + private void hasMissingDefaults() { + MethodControlFlowVisitor visitor = new MethodControlFlowVisitor(); + methodMetrics.getMethod().getMethodDeclaration().accept(visitor); + List switchStatements = visitor.getSwitchStatements(); + for (SwitchStatement singleSwitchStatement : switchStatements) { + if (switchIsMissingDefault(singleSwitchStatement)) { + addToSmells(initializeCodeSmell(MISSING_DEFAULT)); + } + } + } + + private boolean switchIsMissingDefault(SwitchStatement switchStatement) { + List statetmentsOfSwitch = switchStatement.statements(); + for (Statement stm : statetmentsOfSwitch) { + if ((stm instanceof SwitchCase) && ((SwitchCase) stm).isDefault()) { + return false; + } + } + return true; + } + + public List getSmells() { + return smells; + } -public class ImplementationSmellDetector { - - private List smells; - - private MethodMetrics methodMetrics; - private SourceItemInfo info; - private ThresholdsDTO thresholdsDTO; - - private static final String ABST_FUNC_CALL_FRM_CTOR = "Abstract Function Call From Constructor"; - private static final String COMPLEX_CONDITIONAL = "Complex Conditional"; - private static final String COMPLEX_METHOD = "Complex Method"; - private static final String EMPTY_CATCH_CLAUSE = "Empty catch clause"; - private static final String LONG_IDENTIFIER = "Long Identifier"; - private static final String LONG_METHOD = "Long Method"; - private static final String LONG_PARAMETER_LIST = "Long Parameter List"; - private static final String LONG_STATEMENT = "Long Statement"; - private static final String MAGIC_NUMBER = "Magic Number"; - private static final String MISSING_DEFAULT = "Missing default"; - - private static final String AND_OPERATOR_REGEX = "\\&\\&"; - private static final String OR_OPERATOR_REGEX = "\\|\\|"; - private static final Pattern EMPTY_BODY_PATTERN = Pattern.compile("^\\{\\s*\\}\\s*$"); - - public ImplementationSmellDetector(MethodMetrics methodMetrics, SourceItemInfo info) { - this.methodMetrics = methodMetrics; - this.info = info; - - thresholdsDTO = new ThresholdsDTO(); - smells = new ArrayList<>(); - } - - public List detectCodeSmells() { - detectAbstractFunctionCallFromConstructor(); - detectComplexConditional(); - detectComplexMethod(); - detectEmptyCatchBlock(); - detectLongIdentifier(); - detectLongMethod(); - detectLongParameterList(); - detectLongStatement(); - detectMagicNumber(); - detectMissingDefault(); - return smells; - } - - public List detectAbstractFunctionCallFromConstructor() { - if (hasAbstractFunctionCallFromConstructor()) { - addToSmells(initializeCodeSmell(ABST_FUNC_CALL_FRM_CTOR)); - } - return smells; - } - - private boolean hasAbstractFunctionCallFromConstructor() { - SM_Method method = methodMetrics.getMethod(); - if (method.isConstructor()) { - for (SM_Method calledMethod : method.getCalledMethods()) { - if (calledMethod.isAbstract()) { - return true; - } - } - } - return false; - } - - public List detectComplexConditional() { - hasComplexConditional(); - return smells; - } - - private void hasComplexConditional() { - MethodControlFlowVisitor visitor = new MethodControlFlowVisitor(); - methodMetrics.getMethod().getMethodDeclaration().accept(visitor); - for (IfStatement ifStatement : visitor.getIfStatements()) { - if (numOfBooleanSubExpressions(ifStatement) >= thresholdsDTO.getComplexCondition()) { - addToSmells(initializeCodeSmell(COMPLEX_CONDITIONAL)); - } - } - } - - private String getBooleaRegex() { - return AND_OPERATOR_REGEX + "|" + OR_OPERATOR_REGEX; - } - - private int numOfBooleanSubExpressions(IfStatement ifStatement) { - return ifStatement.getExpression().toString().split(getBooleaRegex()).length; - } - - public List detectComplexMethod() { - if (hasComplexMethod()) { - addToSmells(initializeCodeSmell(COMPLEX_METHOD)); - } - return smells; - } - - private boolean hasComplexMethod() { - return methodMetrics.getCyclomaticComplexity() >= thresholdsDTO.getComplexMethod(); - } - - public List detectEmptyCatchBlock() { - MethodControlFlowVisitor visitor = new MethodControlFlowVisitor(); - methodMetrics.getMethod().getMethodDeclaration().accept(visitor); - for (TryStatement tryStatement : visitor.getTryStatements()) { - for (Object catchClause : tryStatement.catchClauses()) { - if (!hasBody((CatchClause) catchClause)) { - addToSmells(initializeCodeSmell(EMPTY_CATCH_CLAUSE)); - } - } - } - return smells; - } - - public boolean hasBody(CatchClause catchClause) { - String body = catchClause.getBody().toString(); - return !EMPTY_BODY_PATTERN.matcher(body).matches(); - } - - public List detectLongIdentifier() { - if (hasLongIdentifier()) { - addToSmells(initializeCodeSmell(LONG_IDENTIFIER)); - } - return smells; - } - - private boolean hasLongIdentifier() { - return hasLongParameter() || hasLongLocalVar() || hasLongFieldAccess(); - } - - private boolean hasLongParameter() { - for (SM_Parameter parameter : methodMetrics.getMethod().getParameterList()) { - if (parameter.getName().length() >= thresholdsDTO.getLongIdentifier()) { - return true; - } - } - return false; - } - - private boolean hasLongLocalVar() { - for (SM_LocalVar var : methodMetrics.getMethod().getLocalVarList()) { - if (var.getName().length() >= thresholdsDTO.getLongIdentifier()) { - return true; - } - } - return false; - } - - private boolean hasLongFieldAccess() { - for (SM_Field field : methodMetrics.getMethod().getDirectFieldAccesses()) { - if (field.getName().length() >= thresholdsDTO.getLongIdentifier()) { - return true; - } - } - return false; - } - - public List detectLongMethod() { - if (hasLongMethod()) { - addToSmells(initializeCodeSmell(LONG_METHOD)); - } - return smells; - } - - private boolean hasLongMethod() { - return methodMetrics.getNumOfLines() >= thresholdsDTO.getLongMethod(); - } - - public List detectLongParameterList() { - if (hasLongParameterList()) { - addToSmells(initializeCodeSmell(LONG_PARAMETER_LIST)); - } - return smells; - } - - private boolean hasLongParameterList() { - return methodMetrics.getNumOfParameters() >= thresholdsDTO.getLongParameterList(); - } - - public List detectLongStatement() { - SM_Method currentMethod = methodMetrics.getMethod(); - if(currentMethod.hasBody()) { - String methodBody = currentMethod.getMethodBody(); - hasLongStatement(methodBody); - } - - return smells; - } - - private void hasLongStatement(String methodBody) { - //FIXME is there another non-hard-coded to replace the "\n" - String[] methodStatements = methodBody.split("\n"); - - for(String singleMethodStatement : methodStatements) { - singleMethodStatement = singleMethodStatement.trim().replaceAll("\\s+", " "); - if(isLongStatement(singleMethodStatement)) { - addToSmells(initializeCodeSmell(LONG_STATEMENT)); - } - } - } - - private boolean isLongStatement(String statement) { - return statement.length() > this.thresholdsDTO.getLongStatement(); - } - - public List detectMagicNumber() { - hasMagicNumbers(); - return smells; - } - - private void hasMagicNumbers() { - NumberLiteralVisitor visitor = new NumberLiteralVisitor(); - methodMetrics.getMethod().getMethodDeclaration().accept(visitor); - List literals = visitor.getNumberLiteralsExpressions(); - - if( literals.size() < 1 ) { - return; - } - - for(NumberLiteral singleNumberLiteral : literals) { - if( isLiteralValid(singleNumberLiteral) ) { - addToSmells(initializeCodeSmell(MAGIC_NUMBER)); - } - } - } - - private boolean isLiteralValid(NumberLiteral singleNumberLiteral) { - boolean isValid = isNotZeroOrOne(singleNumberLiteral) && isNotArrayInitialization(singleNumberLiteral); - return isValid; - } - - // 0s and 1s are not considered as Magic Numbers - private boolean isNotZeroOrOne(NumberLiteral singleNumberLiteral) { - String numberToString = singleNumberLiteral.toString().toLowerCase().replaceAll("_", ""); - double literalValue = 0.0; - try { - // hex case - if(numberToString.startsWith("0x")) { - literalValue = (double)(Long.parseLong(numberToString.replaceAll("0x", "").replaceAll("l", ""),16)); - // long case - } else if(numberToString.endsWith("l")) { - literalValue = (double)(Long.parseLong(numberToString.replaceAll("l", ""))); - // float case - } else if(numberToString.endsWith("f")) { - literalValue = Float.parseFloat(numberToString.replaceAll("f", "")); - } - // double case - else { - literalValue = Double.parseDouble(numberToString); - } - } catch (NumberFormatException ex) { - String logMessage = "Exception while parsing literal number (during Magic Number detection). " + ex.getMessage(); - DJLogger.log(logMessage); - literalValue = 0.0; - } - return literalValue != 0.0 && literalValue != 1.0; - } - - - // Literals in array initializations (such as int[] arr = {0,1};) are not considered as Magic Numbers - private boolean isNotArrayInitialization(NumberLiteral singleNumberLiteral) { - return singleNumberLiteral.getParent().getNodeType() != ASTNode.ARRAY_INITIALIZER; - } - - public List detectMissingDefault() { - hasMissingDefaults(); - return smells; - } - - private void hasMissingDefaults() { - MethodControlFlowVisitor visitor = new MethodControlFlowVisitor(); - methodMetrics.getMethod().getMethodDeclaration().accept(visitor); - List switchStatements = visitor.getSwitchStatements(); - for(SwitchStatement singleSwitchStatement : switchStatements) { - if(switchIsMissingDefault(singleSwitchStatement)) { - addToSmells(initializeCodeSmell(MISSING_DEFAULT)); - } - } - } - - private boolean switchIsMissingDefault(SwitchStatement switchStatement) { - List statetmentsOfSwitch = switchStatement.statements(); - for(Statement stm : statetmentsOfSwitch) { - if ((stm instanceof SwitchCase) && ((SwitchCase)stm).isDefault()) { - return false; - } - } - return true; - } - - public List getSmells() { - return smells; - } - - private ImplementationCodeSmell initializeCodeSmell(String smellName) { - return new ImplementationCodeSmell(info.getProjectName() - , info.getPackageName() - , info.getTypeName() - , info.getMethodName() - , smellName); - } - - private void addToSmells(ImplementationCodeSmell smell) { - smells.add(smell); - } + @Override + public ImplementationCodeSmell initializeCodeSmell(String smellName) { + return new ImplementationCodeSmell(info.getProjectName(), info.getPackageName(), info.getTypeName(), info.getMethodName(), smellName); + } } diff --git a/src/Designite/utils/Constants.java b/src/Designite/utils/Constants.java index d338f7b..e6352a6 100644 --- a/src/Designite/utils/Constants.java +++ b/src/Designite/utils/Constants.java @@ -54,5 +54,5 @@ public class Constants { + ",Method Name" + ",Code Smell" + "\n"; - public static final boolean DEBUG = false; + public static final boolean DEBUG = true; } From 92ba4e9d534ad85e4f972fb43437838c80156cbc Mon Sep 17 00:00:00 2001 From: Mohammed Hashim <55325133+mohammedhashim790@users.noreply.github.com> Date: Thu, 20 Mar 2025 18:16:47 -0300 Subject: [PATCH 3/6] chore: delete .idea directory --- .idea/.gitignore | 8 - .idea/aws.xml | 17 -- .idea/compiler.xml | 13 -- .idea/encodings.xml | 7 - .idea/inspectionProfiles/Project_Default.xml | 8 - .idea/intellij-javadocs-4.0.1.xml | 204 ------------------- .idea/jarRepositories.xml | 20 -- .idea/misc.xml | 11 - .idea/vcs.xml | 6 - 9 files changed, 294 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/aws.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/intellij-javadocs-4.0.1.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/aws.xml b/.idea/aws.xml deleted file mode 100644 index 1d6b5c3..0000000 --- a/.idea/aws.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 5457c84..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 49065d5..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 833f2e1..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/intellij-javadocs-4.0.1.xml b/.idea/intellij-javadocs-4.0.1.xml deleted file mode 100644 index e296157..0000000 --- a/.idea/intellij-javadocs-4.0.1.xml +++ /dev/null @@ -1,204 +0,0 @@ - - - - - UPDATE - false - true - - TYPE - FIELD - METHOD - - - DEFAULT - PUBLIC - PROTECTED - - - - - - ^.*(public|protected|private)*.+interface\s+\w+.* - /**\n - * The interface ${name}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> - */ - - - ^.*(public|protected|private)*.+enum\s+\w+.* - /**\n - * The enum ${name}.\n - */ - - - ^.*(public|protected|private)*.+class\s+\w+.* - /**\n - * The type ${name}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> - */ - - - .+ - /**\n - * The type ${name}.\n - */ - - - - - .+ - /**\n - * Instantiates a new ${name}.\n -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - - - ^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+ - /**\n - * Gets ${partName}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if isNotVoid> - *\n - * @return the ${partName}\n -</#if> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - ^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+ - /**\n - * Sets ${partName}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if isNotVoid> - *\n - * @return the ${partName}\n -</#if> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - ^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+ - /**\n - * The entry point of application.\n - - <#if element.parameterList.parameters?has_content> - *\n -</#if> - * @param ${element.parameterList.parameters[0].name} the input arguments\n -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - .+ - /**\n - * ${name}<#if isNotVoid> ${return}</#if>.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if isNotVoid> - *\n - * @return the ${return}\n -</#if> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - - - ^.*(public|protected|private)*.+static.*(\w\s\w)+.+ - /**\n - * The constant ${element.getName()}.\n - */ - - - ^.*(public|protected|private)*.*(\w\s\w)+.+ - /**\n - <#if element.parent.isInterface()> - * The constant ${element.getName()}.\n -<#else> - * The ${name}.\n -</#if> */ - - - .+ - /**\n - <#if element.parent.isEnum()> - *${name} ${typeName}.\n -<#else> - * The ${name}.\n -</#if>*/ - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index 712ab9d..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 692b19d..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From d51e860e3684432905ecd584fc36f76c1c1bfd1b Mon Sep 17 00:00:00 2001 From: Mohammed Hashim Date: Thu, 20 Mar 2025 18:48:59 -0300 Subject: [PATCH 4/6] chore: deleted .idea* and removed class comment of ArgumentParser. --- .gitignore | 1 + src/Designite/ArgumentParser/ArgumentParser.java | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2beafd4..28bac1f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ csv/ /output +/.idea/ tempDesigniteDebugLog*.txt tempDesigniteLog*.txt diff --git a/src/Designite/ArgumentParser/ArgumentParser.java b/src/Designite/ArgumentParser/ArgumentParser.java index ac12b9a..710c90c 100644 --- a/src/Designite/ArgumentParser/ArgumentParser.java +++ b/src/Designite/ArgumentParser/ArgumentParser.java @@ -2,9 +2,7 @@ import org.apache.commons.cli.Option; -/** - * {@code ArgumentParser} is an abstract class to share responsibility between console and debugging application - */ + public abstract class ArgumentParser { /** * {@code createRequiredOption}. A method to initialise required {@link Option}. From 106ff4e9efa7c34832f5d665507c29fc6d87be03 Mon Sep 17 00:00:00 2001 From: Mohammed Hashim Date: Fri, 21 Mar 2025 16:18:53 -0300 Subject: [PATCH 5/6] chore: Update .gitignore to ignore MAC OS files and .DS_Store banished! --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 4 ++++ 2 files changed, 4 insertions(+) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 31db69803e29f30ca47b205ce63f03e06eba7550..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z-NTe<(r@3Oz1(Em*a!h?fxS3mDOZN=-=7V9b^#HHT8jSzpK}@p+ut z-GIS>HxWAnyWi~m>}Ed5{xHV4vkVRxvlwG-Xowt@3PE$Ft7d``xte3iQa?-PekA>h ziTfEXYK zHje>w7PPyYS2|Tp3=ji9Fo64mfQD!r%r&a513J7uqrZWO0y@4W5QRb8V6G87AY7*c z>Qru?7+j}=U6?rAV6IW8Gp<&KdCbbiO8^XTAL%Kl_6yV@&Ni59 V#97d;(gEorpa`Lk82AMSJ^`EGOq~D# diff --git a/.gitignore b/.gitignore index 28bac1f..d532590 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ */*/*.csv csv/ +# Ignore Mac DS_Store files +.DS_Store +**/.DS_Store + /output /.idea/ From be79a8f5faa813678580ca7c6efbe654c3721d11 Mon Sep 17 00:00:00 2001 From: Mohammed Hashim Date: Fri, 21 Mar 2025 17:09:01 -0300 Subject: [PATCH 6/6] fix(tests): Fixed Failing Test Cases on DesigniteJava. --- .../DesigniteTests/SM_Method_CalledMethodsTests.java | 2 +- tests/DesigniteTests/DesigniteTests/SM_PackageTest.java | 2 +- tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java b/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java index 25871c5..f9c533a 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java +++ b/tests/DesigniteTests/DesigniteTests/SM_Method_CalledMethodsTests.java @@ -70,7 +70,7 @@ public void SM_Method_CalledMethods_staticMethod() { if (method.getCalledMethods().size()==1) { SM_Method calledMethod = method.getCalledMethods().get(0); - assertEquals("DJLogger", calledMethod.getParentType().getName()); + assertEquals("Logger", calledMethod.getParentType().getName()); assertEquals("log", calledMethod.getName()); } } diff --git a/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java b/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java index 3978990..e233339 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_PackageTest.java @@ -28,7 +28,7 @@ public void SM_Package_positive_case() { for (SM_Package pkg : pkgList) { if (pkg.getName().equals("Designite")) - assertEquals(pkg.getTypeList().size(), 2); + assertEquals(pkg.getTypeList().size(), 1); if (pkg.getName().equals("Designite.SourceModel")) //Added additional class in the package. assertEquals(21, pkg.getTypeList().size()); diff --git a/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java b/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java index 394ea63..f16526f 100644 --- a/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java +++ b/tests/DesigniteTests/DesigniteTests/SM_ProjectTest.java @@ -18,7 +18,7 @@ public void testSM_Project_positive_case() { /*for (SM_Package pkg : project.getPackageList()) System.out.println(pkg.getName());*/ - assertEquals(21, project.getPackageCount()); + assertEquals(22, project.getPackageCount()); }