From cd6adb9fdbc06ea4f45ee616296a8be2be47e252 Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Tue, 18 Feb 2025 17:02:16 -0500
Subject: [PATCH 1/8] Issue 103: Add a test application and a
InitializationBlock entity.
Signed-off-by: Rahul Krishna
---
.../cldk/entities/InitializationBlock.java | 22 ++
.../init-blocks-test/.gitattributes | 12 +
.../init-blocks-test/.gitignore | 5 +
.../app/src/main/java/org/example/App.java | 42 +++
.../init-blocks-test/gradle.properties | 7 +
.../init-blocks-test/gradlew | 251 ++++++++++++++++++
.../init-blocks-test/gradlew.bat | 94 +++++++
.../init-blocks-test/settings.gradle.kts | 15 ++
8 files changed, 448 insertions(+)
create mode 100644 src/main/java/com/ibm/cldk/entities/InitializationBlock.java
create mode 100644 src/test/resources/test-applications/init-blocks-test/.gitattributes
create mode 100644 src/test/resources/test-applications/init-blocks-test/.gitignore
create mode 100644 src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
create mode 100644 src/test/resources/test-applications/init-blocks-test/gradle.properties
create mode 100755 src/test/resources/test-applications/init-blocks-test/gradlew
create mode 100644 src/test/resources/test-applications/init-blocks-test/gradlew.bat
create mode 100644 src/test/resources/test-applications/init-blocks-test/settings.gradle.kts
diff --git a/src/main/java/com/ibm/cldk/entities/InitializationBlock.java b/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
new file mode 100644
index 00000000..a5b247ed
--- /dev/null
+++ b/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
@@ -0,0 +1,22 @@
+package com.ibm.cldk.entities;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class InitializationBlock {
+ private String filePath;
+ private String comment;
+ private List annotations;
+ private List thrownExceptions;
+ private String code;
+ private int startLine;
+ private int endLine;
+ private boolean isStatic;
+ private List referencedTypes;
+ private List accessedFields;
+ private List callSites;
+ private List variableDeclarations;
+ private int cyclomaticComplexity;
+}
diff --git a/src/test/resources/test-applications/init-blocks-test/.gitattributes b/src/test/resources/test-applications/init-blocks-test/.gitattributes
new file mode 100644
index 00000000..f91f6460
--- /dev/null
+++ b/src/test/resources/test-applications/init-blocks-test/.gitattributes
@@ -0,0 +1,12 @@
+#
+# https://help.github.com/articles/dealing-with-line-endings/
+#
+# Linux start script should use lf
+/gradlew text eol=lf
+
+# These are Windows script files and should use crlf
+*.bat text eol=crlf
+
+# Binary files should be left untouched
+*.jar binary
+
diff --git a/src/test/resources/test-applications/init-blocks-test/.gitignore b/src/test/resources/test-applications/init-blocks-test/.gitignore
new file mode 100644
index 00000000..1b6985c0
--- /dev/null
+++ b/src/test/resources/test-applications/init-blocks-test/.gitignore
@@ -0,0 +1,5 @@
+# Ignore Gradle project-specific cache directory
+.gradle
+
+# Ignore Gradle build output directory
+build
diff --git a/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java b/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
new file mode 100644
index 00000000..5807917b
--- /dev/null
+++ b/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
@@ -0,0 +1,42 @@
+package org.example;
+
+import java.util.List;
+
+public class App {
+ private static String staticMessage;
+
+ static {
+ try {
+ staticMessage = "Static block initialized";
+ System.out.println("Static initialization block executed.");
+ initializeStaticFields();
+ } catch (Exception e) {
+ System.err.println("Error in static block: " + e.getMessage());
+ }
+ }
+
+ {
+ try {
+ System.out.println("Instance initialization block executed.");
+ initializeInstanceFields();
+ } catch (Exception e) {
+ System.err.println("Error in instance block: " + e.getMessage());
+ }
+ }
+
+ public App() {
+ System.out.println("Constructor executed.");
+ }
+
+ private static void initializeStaticFields() {
+ System.out.println("Initializing static fields.");
+ }
+
+ private void initializeInstanceFields() {
+ System.out.println("Initializing instance fields.");
+ }
+
+ public static void main(String[] args) {
+ new App();
+ }
+}
diff --git a/src/test/resources/test-applications/init-blocks-test/gradle.properties b/src/test/resources/test-applications/init-blocks-test/gradle.properties
new file mode 100644
index 00000000..51540088
--- /dev/null
+++ b/src/test/resources/test-applications/init-blocks-test/gradle.properties
@@ -0,0 +1,7 @@
+# This file was generated by the Gradle 'init' task.
+# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
+
+org.gradle.configuration-cache=true
+org.gradle.parallel=true
+org.gradle.caching=true
+
diff --git a/src/test/resources/test-applications/init-blocks-test/gradlew b/src/test/resources/test-applications/init-blocks-test/gradlew
new file mode 100755
index 00000000..f3b75f3b
--- /dev/null
+++ b/src/test/resources/test-applications/init-blocks-test/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/src/test/resources/test-applications/init-blocks-test/gradlew.bat b/src/test/resources/test-applications/init-blocks-test/gradlew.bat
new file mode 100644
index 00000000..9d21a218
--- /dev/null
+++ b/src/test/resources/test-applications/init-blocks-test/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/test/resources/test-applications/init-blocks-test/settings.gradle.kts b/src/test/resources/test-applications/init-blocks-test/settings.gradle.kts
new file mode 100644
index 00000000..d1bf0739
--- /dev/null
+++ b/src/test/resources/test-applications/init-blocks-test/settings.gradle.kts
@@ -0,0 +1,15 @@
+package `test-applications`.`init-blocks-test`/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.12.1/userguide/multi_project_builds.html in the Gradle documentation.
+ * This project uses @Incubating APIs which are subject to change.
+ */
+
+plugins {
+ // Apply the foojay-resolver plugin to allow automatic download of JDKs
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
+}
+
+rootProject.name = "record-class-test"
+include("app")
From 8ced60a2a2b6f851d68eaf2a04496a8a0813a6dd Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Tue, 18 Feb 2025 17:59:51 -0500
Subject: [PATCH 2/8] Issue 103: Add implementation for Initialization Blocks
modeled on Callable.
Signed-off-by: Rahul Krishna
---
settings.gradle | 10 +---
src/main/java/com/ibm/cldk/SymbolTable.java | 56 +++++++++++++++++--
.../cldk/entities/InitializationBlock.java | 2 +
src/main/java/com/ibm/cldk/entities/Type.java | 1 +
.../ibm/cldk/CodeAnalyzerIntegrationTest.java | 32 +++++++++++
.../app/src/main/java/org/example/App.java | 1 +
6 files changed, 89 insertions(+), 13 deletions(-)
diff --git a/settings.gradle b/settings.gradle
index a6b36d6a..2d7bdb7d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -9,12 +9,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-*/
-pluginManagement {
- plugins {
- id 'org.jetbrains.kotlin.jvm' version '2.1.10'
- }
-}
-plugins {
- id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
-}
+*/
\ No newline at end of file
diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java
index 2dbef6d2..130ac98c 100644
--- a/src/main/java/com/ibm/cldk/SymbolTable.java
+++ b/src/main/java/com/ibm/cldk/SymbolTable.java
@@ -15,11 +15,13 @@
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
+import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.github.javaparser.symbolsolver.utils.SymbolSolverCollectionStrategy;
@@ -172,9 +174,16 @@ else if (typeDecl instanceof RecordDeclaration) {
typeNode = new com.ibm.cldk.entities.Type();
}
- /* set common attributes of types that available in type declarations:
- is nested type, is class or interface declaration, is enum declaration,
- comments, parent class, callable declarations, field declarations */
+ /* set common attributes of types that available in type declarations:
+ is nested type, is class or interface declaration, is enum declaration,
+ comments, parent class, callable declarations, field declarations
+ */
+ // Discover initialization blocks
+ typeNode.setInitializationBlocks(typeDecl.findAll(InitializerDeclaration.class).stream()
+ .map(initializerDeclaration -> {
+ return createInitializationBlock(initializerDeclaration, parseResult.getStorage().map(s -> s.getPath().toString()).orElse(""));
+ })
+ .collect(Collectors.toList()));
// Set fields indicating nested, class/interface, enum, annotation, and record types
typeNode.setNestedType(typeDecl.isNestedType());
typeNode.setClassOrInterfaceDeclaration(typeDecl.isClassOrInterfaceDeclaration());
@@ -213,7 +222,38 @@ else if (typeDecl instanceof RecordDeclaration) {
return cUnit;
}
-
+ private static InitializationBlock createInitializationBlock(InitializerDeclaration initializerDeclaration, String filePath) {
+ InitializationBlock initializationBlock = new InitializationBlock();
+ initializationBlock.setFilePath(filePath);
+ initializationBlock.setComment(initializerDeclaration.getComment().isPresent() ? initializerDeclaration.getComment().get().asString() : "");
+ initializationBlock.setAnnotations(initializerDeclaration.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
+ // add exceptions declared in "throws" clause
+ initializationBlock.setThrownExceptions(initializerDeclaration.getBody().getStatements().stream().filter(Statement::isThrowStmt).map(throwStmt -> {
+ try {
+ return javaSymbolSolver.calculateType(throwStmt.asThrowStmt().getExpression()).describe();
+ } catch (Exception e) {
+ return throwStmt.asThrowStmt().getExpression().toString();
+ }
+ }).collect(Collectors.toList()));
+ initializationBlock.setCode(initializerDeclaration.getBody().toString());
+ initializationBlock.setStartLine(initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().begin.line : -1);
+ initializationBlock.setEndLine(initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().end.line : -1);
+ initializationBlock.setStatic(initializerDeclaration.isStatic());
+ initializationBlock.setReferencedTypes(getReferencedTypes(Optional.ofNullable(initializerDeclaration.getBody())));
+ initializationBlock.setAccessedFields(getAccessedFields(Optional.ofNullable(initializerDeclaration.getBody()), Collections.emptyList(), ""));
+ initializationBlock.setCallSites(getCallSites(Optional.ofNullable(initializerDeclaration.getBody())));
+ initializationBlock.setVariableDeclarations(getVariableDeclarations(Optional.ofNullable(initializerDeclaration.getBody())));
+ initializationBlock.setCyclomaticComplexity(getCyclomaticComplexity(initializerDeclaration));
+ return initializationBlock;
+ }
+ /**
+ * Processes the given record to extract information about the
+ * declared field and returns a JSON object containing the extracted
+ * information.
+ *
+ * @param recordDecl field declaration to be processed
+ * @return Field object containing extracted information
+ */
private static List processRecordComponents(RecordDeclaration recordDecl) {
return recordDecl.getParameters().stream().map(
parameter -> {
@@ -568,6 +608,14 @@ private static int getCyclomaticComplexity(CallableDeclaration callableDeclarati
int catchClauseCount = callableDeclaration.findAll(CatchClause.class).size();
return ifStmtCount + loopStmtCount + switchCaseCount + conditionalExprCount + catchClauseCount + 1;
}
+ private static int getCyclomaticComplexity(InitializerDeclaration initializerDeclaration) {
+ int ifStmtCount = initializerDeclaration.findAll(IfStmt.class).size();
+ int loopStmtCount = initializerDeclaration.findAll(DoStmt.class).size() + initializerDeclaration.findAll(ForStmt.class).size() + initializerDeclaration.findAll(ForEachStmt.class).size() + initializerDeclaration.findAll(WhileStmt.class).size();
+ int switchCaseCount = initializerDeclaration.findAll(SwitchStmt.class).stream().map(stmt -> stmt.getEntries().size()).reduce(0, Integer::sum);
+ int conditionalExprCount = initializerDeclaration.findAll(ConditionalExpr.class).size();
+ int catchClauseCount = initializerDeclaration.findAll(CatchClause.class).size();
+ return ifStmtCount + loopStmtCount + switchCaseCount + conditionalExprCount + catchClauseCount + 1;
+ }
/**
* Processes the given field declaration to extract information about the
diff --git a/src/main/java/com/ibm/cldk/entities/InitializationBlock.java b/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
index a5b247ed..eab76d2b 100644
--- a/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
+++ b/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
@@ -3,6 +3,7 @@
import lombok.Data;
import java.util.List;
+import java.util.stream.Collector;
@Data
public class InitializationBlock {
@@ -19,4 +20,5 @@ public class InitializationBlock {
private List callSites;
private List variableDeclarations;
private int cyclomaticComplexity;
+
}
diff --git a/src/main/java/com/ibm/cldk/entities/Type.java b/src/main/java/com/ibm/cldk/entities/Type.java
index d6ce7290..8fc5ab6b 100644
--- a/src/main/java/com/ibm/cldk/entities/Type.java
+++ b/src/main/java/com/ibm/cldk/entities/Type.java
@@ -28,5 +28,6 @@ public class Type {
private List fieldDeclarations = new ArrayList<>();
private List enumConstants = new ArrayList<>();
private List recordComponents = new ArrayList<>();
+ private List initializationBlocks = new ArrayList<>();
private boolean isEntrypointClass = false;
}
\ No newline at end of file
diff --git a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java
index 24f1975a..118201dc 100644
--- a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java
+++ b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java
@@ -55,6 +55,7 @@ public class CodeAnalyzerIntegrationTest {
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/plantsbywebsphere")), "/test-applications/plantsbywebsphere")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/call-graph-test")), "/test-applications/call-graph-test")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/record-class-test")), "/test-applications/record-class-test")
+ .withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/init-blocks-test")), "/test-applications/init-blocks-test")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/mvnw-working-test")), "/test-applications/mvnw-working-test");
@Container
@@ -332,4 +333,35 @@ void parametersInCallableMustHaveStartAndEndLineAndColumns() throws IOException,
}
}
}
+
+ @Test
+ void mustBeAbleToResolveInitializationBlocks() throws IOException, InterruptedException {
+ var runCodeAnalyzerOnCallGraphTest = container.execInContainer(
+ "bash", "-c",
+ String.format(
+ "export JAVA_HOME=%s && java -jar /opt/jars/codeanalyzer-%s.jar --input=/test-applications/init-blocks-test --analysis-level=1",
+ javaHomePath, codeanalyzerVersion
+ )
+ );
+
+ // Read the output JSON
+ Gson gson = new Gson();
+ JsonObject jsonObject = gson.fromJson(runCodeAnalyzerOnCallGraphTest.getStdout(), JsonObject.class);
+ JsonObject symbolTable = jsonObject.getAsJsonObject("symbol_table");
+ for (Map.Entry element : symbolTable.entrySet()) {
+ String key = element.getKey();
+ if (!key.endsWith("App.java")) {
+ continue;
+ }
+ JsonObject type = element.getValue().getAsJsonObject();
+ if (type.has("type_declarations")) {
+ JsonObject typeDeclarations = type.getAsJsonObject("type_declarations");
+ JsonArray initializationBlocks = typeDeclarations.getAsJsonObject("org.example.App").getAsJsonArray("initialization_blocks");
+ // There should be 2 blocks
+ Assertions.assertEquals(2, initializationBlocks.size(), "Callable should have 1 parameter");
+ Assertions.assertTrue(initializationBlocks.get(0).getAsJsonObject().get("is_static").getAsBoolean(), "Static block should be marked as static");
+ Assertions.assertFalse(initializationBlocks.get(1).getAsJsonObject().get("is_static").getAsBoolean(), "Instance block should be marked as not static");
+ }
+ }
+ }
}
diff --git a/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java b/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
index 5807917b..7fbb3f07 100644
--- a/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
+++ b/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
@@ -12,6 +12,7 @@ public class App {
initializeStaticFields();
} catch (Exception e) {
System.err.println("Error in static block: " + e.getMessage());
+ throw new RuntimeException(e);
}
}
From cf59b6b84e39eb32370c3084d86630647e975436 Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Tue, 18 Feb 2025 18:04:09 -0500
Subject: [PATCH 3/8] Issue 103: Add implementation for Initialization Blocks
modeled on Callable.
Signed-off-by: Rahul Krishna
---
.github/workflows/release_config.json | 4 ++++
settings.gradle | 10 +++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/release_config.json b/.github/workflows/release_config.json
index 7ddd165c..f0d4b5b2 100644
--- a/.github/workflows/release_config.json
+++ b/.github/workflows/release_config.json
@@ -27,6 +27,10 @@
{
"title": "## \uD83D\uDEE0 Other Updates",
"labels": ["other", "kind/dependency-change"]
+ },
+ {
+ "title": "## 🚨 Breaking Changes",
+ "labels": ["breaking"]
}
],
"ignore_labels": [
diff --git a/settings.gradle b/settings.gradle
index 2d7bdb7d..a6b36d6a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -9,4 +9,12 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-*/
\ No newline at end of file
+*/
+pluginManagement {
+ plugins {
+ id 'org.jetbrains.kotlin.jvm' version '2.1.10'
+ }
+}
+plugins {
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
+}
From c60958bf04b1680b380d1dd94e17775be5b3b88a Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Tue, 18 Feb 2025 18:09:07 -0500
Subject: [PATCH 4/8] Issue 103: Remove unused imports.
Signed-off-by: Rahul Krishna
---
src/main/java/com/ibm/cldk/SymbolTable.java | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java
index 130ac98c..3d43d384 100644
--- a/src/main/java/com/ibm/cldk/SymbolTable.java
+++ b/src/main/java/com/ibm/cldk/SymbolTable.java
@@ -15,13 +15,11 @@
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;
-import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
-import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.github.javaparser.symbolsolver.utils.SymbolSolverCollectionStrategy;
@@ -35,7 +33,6 @@
import com.ibm.cldk.entities.*;
import com.ibm.cldk.utils.Log;
import org.apache.commons.lang3.tuple.Pair;
-import org.checkerframework.checker.units.qual.C;
import java.io.IOException;
import java.nio.file.Path;
From 5669284e94f25a9f7bf8d5a842aa06498d0e4f23 Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Wed, 19 Feb 2025 12:14:17 -0500
Subject: [PATCH 5/8] Issue 102: Add comment nodes eveywhere. Add a new field
in CompilationUnit to capture package information.
Signed-off-by: Rahul Krishna
---
src/main/java/com/ibm/cldk/SymbolTable.java | 915 ++++++++++++------
.../java/com/ibm/cldk/entities/CallSite.java | 59 +-
.../java/com/ibm/cldk/entities/Callable.java | 77 +-
.../java/com/ibm/cldk/entities/Field.java | 2 +-
.../cldk/entities/InitializationBlock.java | 2 +-
.../cldk/entities/JavaCompilationUnit.java | 3 +-
.../cldk/entities/ParameterInCallable.java | 48 +-
.../ibm/cldk/entities/RecordComponent.java | 48 +-
src/main/java/com/ibm/cldk/entities/Type.java | 50 +-
.../cldk/entities/VariableDeclaration.java | 55 +-
.../com/ibm/cldk/utils/AnalysisUtils.java | 3 +-
.../ibm/cldk/CodeAnalyzerIntegrationTest.java | 28 +
.../app/src/main/java/org/example/App.java | 45 +-
13 files changed, 1014 insertions(+), 321 deletions(-)
diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java
index 3d43d384..f99b38c6 100644
--- a/src/main/java/com/ibm/cldk/SymbolTable.java
+++ b/src/main/java/com/ibm/cldk/SymbolTable.java
@@ -1,5 +1,28 @@
package com.ibm.cldk;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import com.github.javaparser.ast.body.*;
+import com.github.javaparser.ast.comments.Comment;
+import com.github.javaparser.ast.comments.JavadocComment;
+import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc;
+import com.github.javaparser.ast.stmt.*;
+import org.apache.commons.lang3.tuple.Pair;
+
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ParserConfiguration;
@@ -8,10 +31,16 @@
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
-import com.github.javaparser.ast.body.*;
-import com.github.javaparser.ast.expr.*;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.AssignExpr;
+import com.github.javaparser.ast.expr.CastExpr;
+import com.github.javaparser.ast.expr.ConditionalExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.nodeTypes.NodeWithName;
-import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;
@@ -27,19 +56,22 @@
import com.github.javaparser.utils.SourceRoot;
import com.google.common.collect.Table;
import com.google.common.collect.Tables;
+import com.ibm.cldk.entities.CRUDOperation;
+import com.ibm.cldk.entities.CRUDQuery;
+import com.ibm.cldk.entities.CallSite;
+import com.ibm.cldk.entities.Callable;
+import com.ibm.cldk.entities.EnumConstant;
+import com.ibm.cldk.entities.Field;
+import com.ibm.cldk.entities.InitializationBlock;
+import com.ibm.cldk.entities.JavaCompilationUnit;
+import com.ibm.cldk.entities.ParameterInCallable;
+import com.ibm.cldk.entities.RecordComponent;
+import com.ibm.cldk.entities.VariableDeclaration;
import com.ibm.cldk.javaee.CRUDFinderFactory;
import com.ibm.cldk.javaee.utils.enums.CRUDOperationType;
import com.ibm.cldk.javaee.utils.enums.CRUDQueryType;
-import com.ibm.cldk.entities.*;
import com.ibm.cldk.utils.Log;
-import org.apache.commons.lang3.tuple.Pair;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import org.jetbrains.annotations.NotNull;
@SuppressWarnings("rawtypes")
public class SymbolTable {
@@ -57,192 +89,307 @@ public class SymbolTable {
* @return JSON object containing extracted information
*/
// Let's store the known callables here for future use.
- public static Table declaredMethodsAndConstructors = Tables.newCustomTable(new HashMap<>(), () -> new HashMap<>() {
- @Override
- public Callable get(Object key) {
- if (key instanceof String) {
- Optional> matchingEntry = this.entrySet().stream().filter(entry -> isMethodSignatureMatch((String) key, entry.getKey())).findFirst();
- if (matchingEntry.isPresent()) {
- return matchingEntry.get().getValue();
+ public static Table declaredMethodsAndConstructors = Tables
+ .newCustomTable(new HashMap<>(), () -> new HashMap<>() {
+ @Override
+ public Callable get(Object key) {
+ if (key instanceof String) {
+ Optional> matchingEntry = this.entrySet().stream()
+ .filter(entry -> isMethodSignatureMatch((String) key, entry.getKey())).findFirst();
+ if (matchingEntry.isPresent()) {
+ return matchingEntry.get().getValue();
+ }
+ }
+ return super.get(key);
}
- }
- return super.get(key);
- }
- private boolean isMethodSignatureMatch(String fullSignature, String searchSignature) {
- String methodName = fullSignature.split("\\(")[0];
- String searchMethodName = searchSignature.split("\\(")[0];
+ private boolean isMethodSignatureMatch(String fullSignature, String searchSignature) {
+ String methodName = fullSignature.split("\\(")[0];
+ String searchMethodName = searchSignature.split("\\(")[0];
- // Check method name match
- if (!methodName.equals(searchMethodName)) {
- return false;
- }
+ // Check method name match
+ if (!methodName.equals(searchMethodName)) {
+ return false;
+ }
- // Extract parameters, split by comma, and trim
- String[] fullParams = fullSignature.substring(fullSignature.indexOf("(") + 1, fullSignature.lastIndexOf(")")).split(",");
- String[] searchParams = searchSignature.substring(searchSignature.indexOf("(") + 1, searchSignature.lastIndexOf(")")).split(",");
+ // Extract parameters, split by comma, and trim
+ String[] fullParams = fullSignature
+ .substring(fullSignature.indexOf("(") + 1, fullSignature.lastIndexOf(")")).split(",");
+ String[] searchParams = searchSignature
+ .substring(searchSignature.indexOf("(") + 1, searchSignature.lastIndexOf(")")).split(",");
- // Allow matching with fewer search parameters
- if (searchParams.length != fullParams.length) {
- return false;
- }
+ // Allow matching with fewer search parameters
+ if (searchParams.length != fullParams.length) {
+ return false;
+ }
- return IntStream.range(0, searchParams.length).allMatch(i -> {
- String fullParamTrimmed = fullParams[i].trim();
- String searchParamTrimmed = searchParams[i].trim();
- return fullParamTrimmed.endsWith(searchParamTrimmed);
+ return IntStream.range(0, searchParams.length).allMatch(i -> {
+ String fullParamTrimmed = fullParams[i].trim();
+ String searchParamTrimmed = searchParams[i].trim();
+ return fullParamTrimmed.endsWith(searchParamTrimmed);
+ });
+ }
});
- }
- });
private static JavaCompilationUnit processCompilationUnit(CompilationUnit parseResult) {
JavaCompilationUnit cUnit = new JavaCompilationUnit();
cUnit.setFilePath(parseResult.getStorage().map(s -> s.getPath().toString()).orElse(""));
- // Add the comment field to the compilation unit
- cUnit.setComment(parseResult.getComment().isPresent() ? parseResult.getComment().get().asString() : "");
-
+ com.ibm.cldk.entities.Comment comment = new com.ibm.cldk.entities.Comment();
+
+ // Add class comment
+ cUnit.setComments(
+ parseResult.getAllContainedComments().stream().map(c -> {
+ com.ibm.cldk.entities.Comment fileComment = new com.ibm.cldk.entities.Comment();
+ fileComment.setContent(c.getContent());
+ fileComment.setStartLine(c.getRange().isPresent() ? c.getRange().get().begin.line : -1);
+ fileComment.setEndLine(c.getRange().isPresent() ? c.getRange().get().end.line : -1);
+ fileComment.setStartColumn(c.getRange().isPresent() ? c.getRange().get().begin.column : -1);
+ fileComment.setEndColumn(c.getRange().isPresent() ? c.getRange().get().end.column : -1);
+ fileComment.setJavadoc(c.isJavadocComment());
+ return fileComment;
+ })
+ .collect(Collectors.toList()));
+
+ // Set package name
+ cUnit.setPackageName(parseResult.getPackageDeclaration().map(NodeWithName::getNameAsString).orElse(""));
+
+ // Add javadoc comment
// Add imports
- cUnit.setImports(parseResult.getImports().stream().map(NodeWithName::getNameAsString).collect(Collectors.toList()));
+ cUnit.setImports(
+ parseResult.getImports().stream().map(NodeWithName::getNameAsString).collect(Collectors.toList()));
// create array node for type declarations
- cUnit.setTypeDeclarations(parseResult.findAll(TypeDeclaration.class).stream().filter(typeDecl -> typeDecl.getFullyQualifiedName().isPresent()).map(typeDecl -> {
- // get type name and initialize the type object
- String typeName = typeDecl.getFullyQualifiedName().get().toString();
- com.ibm.cldk.entities.Type typeNode = new com.ibm.cldk.entities.Type();;
- if (typeDecl instanceof ClassOrInterfaceDeclaration) {
- ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl;
-
- // Add interfaces implemented by class
- typeNode.setImplementsList(classDecl.getImplementedTypes().stream().map(SymbolTable::resolveType).collect(Collectors.toList()));
-
- // Add class modifiers
- typeNode.setModifiers(classDecl.getModifiers().stream().map(m -> m.toString().strip()).collect(Collectors.toList()));
-
- // Add class annotations
- typeNode.setAnnotations(classDecl.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
-
- // add booleans indicating interfaces and inner/local classes
- typeNode.setInterface(classDecl.isInterface());
- typeNode.setInnerClass(classDecl.isInnerClass());
- typeNode.setLocalClass(classDecl.isLocalClassDeclaration());
-
- // Add extends
- typeNode.setExtendsList(classDecl.getExtendedTypes().stream().map(SymbolTable::resolveType).collect(Collectors.toList()));
-
- } else if (typeDecl instanceof EnumDeclaration) {
- EnumDeclaration enumDecl = (EnumDeclaration) typeDecl;
-
- // Add interfaces implemented by enum
- typeNode.setImplementsList(enumDecl.getImplementedTypes().stream().map(SymbolTable::resolveType).collect(Collectors.toList()));
-
- // Add enum modifiers
- typeNode.setModifiers(enumDecl.getModifiers().stream().map(m -> m.toString().strip()).collect(Collectors.toList()));
-
- // Add enum annotations
- typeNode.setAnnotations(enumDecl.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
-
- // Add enum constants
- typeNode.setEnumConstants(enumDecl.getEntries().stream().map(SymbolTable::processEnumConstantDeclaration).collect(Collectors.toList()));
- }
- else if (typeDecl instanceof RecordDeclaration) {
- RecordDeclaration recordDecl = (RecordDeclaration) typeDecl;
-
- // Set that this is a record declaration
- typeNode.setRecordDeclaration(typeDecl.isRecordDeclaration());
-
- // Add interfaces implemented by record
- typeNode.setImplementsList(recordDecl.getImplementedTypes().stream().map(SymbolTable::resolveType).collect(Collectors.toList()));
-
- // Add record modifiers
- typeNode.setModifiers(recordDecl.getModifiers().stream().map(m -> m.toString().strip()).collect(Collectors.toList()));
-
- // Add record annotations
- typeNode.setAnnotations(recordDecl.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
-
- // Add record components
- typeNode.setRecordComponents(processRecordComponents(recordDecl));
- }
- else {
- // TODO: handle AnnotationDeclaration, RecordDeclaration
- // set the common type attributes only
- Log.warn("Found unsupported type declaration: " + typeDecl.toString());
- typeNode = new com.ibm.cldk.entities.Type();
- }
-
- /* set common attributes of types that available in type declarations:
- is nested type, is class or interface declaration, is enum declaration,
- comments, parent class, callable declarations, field declarations
- */
- // Discover initialization blocks
- typeNode.setInitializationBlocks(typeDecl.findAll(InitializerDeclaration.class).stream()
- .map(initializerDeclaration -> {
- return createInitializationBlock(initializerDeclaration, parseResult.getStorage().map(s -> s.getPath().toString()).orElse(""));
- })
- .collect(Collectors.toList()));
- // Set fields indicating nested, class/interface, enum, annotation, and record types
- typeNode.setNestedType(typeDecl.isNestedType());
- typeNode.setClassOrInterfaceDeclaration(typeDecl.isClassOrInterfaceDeclaration());
- typeNode.setEnumDeclaration(typeDecl.isEnumDeclaration());
- typeNode.setAnnotationDeclaration(typeDecl.isAnnotationDeclaration());
-
- // Add class comment
- typeNode.setComment(typeDecl.getComment().isPresent() ? typeDecl.getComment().get().asString() : "");
-
- // add parent class (for nested type declarations)
- typeNode.setParentType(typeDecl.getParentNode().get() instanceof TypeDeclaration ? ((TypeDeclaration>) typeDecl.getParentNode().get()).getFullyQualifiedName().get() : "");
-
- typeNode.setNestedTypeDeclarations(typeDecl.findAll(TypeDeclaration.class).stream().filter(typ -> typ.isClassOrInterfaceDeclaration() || typ.isEnumDeclaration()).filter(typ -> typ.getParentNode().isPresent() && typ.getParentNode().get() == typeDecl).map(typ -> typ.getFullyQualifiedName().get().toString()).collect(Collectors.toList()));
-
- // Add information about declared fields (filtering to fields declared in the
- // type, not in a nested type)
- typeNode.setFieldDeclarations(typeDecl.findAll(FieldDeclaration.class).stream().filter(f -> f.getParentNode().isPresent() && f.getParentNode().get() == typeDecl).map(SymbolTable::processFieldDeclaration).collect(Collectors.toList()));
- List fieldNames = new ArrayList<>();
- typeNode.getFieldDeclarations().stream().map(fd -> fd.getVariables()).forEach(fieldNames::addAll);
-
- // Add information about declared methods (filtering to methods declared in the class, not in a nested class)
- typeNode.setCallableDeclarations(typeDecl.findAll(CallableDeclaration.class).stream().filter(c -> c.getParentNode().isPresent() && c.getParentNode().get() == typeDecl).map(meth -> {
- Pair callableDeclaration = processCallableDeclaration(meth, fieldNames, typeName, parseResult.getStorage().map(s -> s.getPath().toString()).orElse(""));
- declaredMethodsAndConstructors.put(typeName, callableDeclaration.getLeft(), callableDeclaration.getRight());
- return callableDeclaration;
- }).collect(Collectors.toMap(p -> p.getLeft(), p -> p.getRight())));
-
- // Add information about if the TypeNode is an entry point class
- typeNode.setEntrypointClass(isEntryPointClass(typeDecl));
-
- return Pair.of(typeName, typeNode);
+ cUnit.setTypeDeclarations(parseResult.findAll(TypeDeclaration.class).stream()
+ .filter(typeDecl -> typeDecl.getFullyQualifiedName().isPresent()).map(typeDecl -> {
+ // get type name and initialize the type object
+ String typeName = typeDecl.getFullyQualifiedName().get().toString();
+ com.ibm.cldk.entities.Type typeNode = new com.ibm.cldk.entities.Type();
+
+ if (typeDecl instanceof ClassOrInterfaceDeclaration) {
+ ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl;
+
+ // Add interfaces implemented by class
+ typeNode.setImplementsList(classDecl.getImplementedTypes().stream()
+ .map(SymbolTable::resolveType).collect(Collectors.toList()));
+
+ // Add class modifiers
+ typeNode.setModifiers(classDecl.getModifiers().stream().map(m -> m.toString().strip())
+ .collect(Collectors.toList()));
+
+ // Add class annotations
+ typeNode.setAnnotations(classDecl.getAnnotations().stream().map(a -> a.toString().strip())
+ .collect(Collectors.toList()));
+
+ // add booleans indicating interfaces and inner/local classes
+ typeNode.setInterface(classDecl.isInterface());
+ typeNode.setInnerClass(classDecl.isInnerClass());
+ typeNode.setLocalClass(classDecl.isLocalClassDeclaration());
+
+ // Add extends
+ typeNode.setExtendsList(classDecl.getExtendedTypes().stream().map(SymbolTable::resolveType)
+ .collect(Collectors.toList()));
+
+ } else if (typeDecl instanceof EnumDeclaration) {
+ EnumDeclaration enumDecl = (EnumDeclaration) typeDecl;
+
+ // Add interfaces implemented by enum
+ typeNode.setImplementsList(enumDecl.getImplementedTypes().stream().map(SymbolTable::resolveType)
+ .collect(Collectors.toList()));
+
+ // Add enum modifiers
+ typeNode.setModifiers(enumDecl.getModifiers().stream().map(m -> m.toString().strip())
+ .collect(Collectors.toList()));
+
+ // Add enum annotations
+ typeNode.setAnnotations(enumDecl.getAnnotations().stream().map(a -> a.toString().strip())
+ .collect(Collectors.toList()));
+
+ // Add enum constants
+ typeNode.setEnumConstants(enumDecl.getEntries().stream()
+ .map(SymbolTable::processEnumConstantDeclaration).collect(Collectors.toList()));
+ } else if (typeDecl instanceof RecordDeclaration) {
+ RecordDeclaration recordDecl = (RecordDeclaration) typeDecl;
+
+ // Set that this is a record declaration
+ typeNode.setRecordDeclaration(typeDecl.isRecordDeclaration());
+
+ // Add interfaces implemented by record
+ typeNode.setImplementsList(recordDecl.getImplementedTypes().stream()
+ .map(SymbolTable::resolveType).collect(Collectors.toList()));
+
+ // Add record modifiers
+ typeNode.setModifiers(recordDecl.getModifiers().stream().map(m -> m.toString().strip())
+ .collect(Collectors.toList()));
+
+ // Add record annotations
+ typeNode.setAnnotations(recordDecl.getAnnotations().stream().map(a -> a.toString().strip())
+ .collect(Collectors.toList()));
+
+ // Add record components
+ typeNode.setRecordComponents(processRecordComponents(recordDecl));
+ } else {
+ // TODO: handle AnnotationDeclaration, RecordDeclaration
+ // set the common type attributes only
+ Log.warn("Found unsupported type declaration: " + typeDecl.toString());
+ typeNode = new com.ibm.cldk.entities.Type();
+ }
+ /*
+ * set common attributes of types that available in type declarations:
+ * is nested type, is class or interface declaration, is enum declaration,
+ * comments, parent class, callable declarations, field declarations
+ */
+ // Discover initialization blocks
+ typeNode.setInitializationBlocks(typeDecl.findAll(InitializerDeclaration.class).stream()
+ .map(initializerDeclaration -> {
+ return createInitializationBlock(initializerDeclaration, parseResult.getStorage()
+ .map(s -> s.getPath().toString()).orElse(""));
+ })
+ .collect(Collectors.toList()));
+ // Set fields indicating nested, class/interface, enum, annotation, and record
+ // types
+ typeNode.setNestedType(typeDecl.isNestedType());
+ typeNode.setClassOrInterfaceDeclaration(typeDecl.isClassOrInterfaceDeclaration());
+ typeNode.setEnumDeclaration(typeDecl.isEnumDeclaration());
+ typeNode.setAnnotationDeclaration(typeDecl.isAnnotationDeclaration());
+
+ // Add class comment
+ typeNode.setComments(
+ typeDecl.getAllContainedComments().stream()
+// .filter(c -> c.getParentNode().isEmpty() || (c.getParentNode().isPresent() && parseResult.getPrimaryType().get().equals(c.getCommentedNode().get())))
+ .map(c -> {
+ com.ibm.cldk.entities.Comment typeNodeComment = new com.ibm.cldk.entities.Comment();
+ typeNodeComment.setContent(c.getContent());
+ typeNodeComment.setStartLine(c.getRange().isPresent() ? c.getRange().get().begin.line : -1);
+ typeNodeComment.setEndLine(c.getRange().isPresent() ? c.getRange().get().end.line : -1);
+ typeNodeComment.setStartColumn(c.getRange().isPresent() ? c.getRange().get().begin.column : -1);
+ typeNodeComment.setEndColumn(c.getRange().isPresent() ? c.getRange().get().end.column : -1);
+ typeNodeComment.setJavadoc(c.isJavadocComment());
+ return typeNodeComment;
+ })
+ .collect(Collectors.toList()));
+
+ // Get JavaDoc comments
+ // Check to see if there is a java doc comment if so, add it to the comments list
+ if (getJavadoc(typeDecl).isPresent()) {
+ typeNode.getComments().add(getJavadoc(typeDecl).get());
+ }
- }).collect(Collectors.toMap(p -> p.getLeft(), p -> p.getRight())));
+ // add parent class (for nested type declarations)
+ typeNode.setParentType(typeDecl.getParentNode().get() instanceof TypeDeclaration
+ ? ((TypeDeclaration>) typeDecl.getParentNode().get())
+ .getFullyQualifiedName().get()
+ : "");
+
+ typeNode.setNestedTypeDeclarations(typeDecl.findAll(TypeDeclaration.class).stream()
+ .filter(typ -> typ.isClassOrInterfaceDeclaration() || typ.isEnumDeclaration())
+ .filter(typ -> typ.getParentNode().isPresent() && typ.getParentNode().get() == typeDecl)
+ .map(typ -> typ.getFullyQualifiedName().get().toString()).collect(Collectors.toList()));
+
+ // Add information about declared fields (filtering to fields declared in the
+ // type, not in a nested type)
+ typeNode.setFieldDeclarations(typeDecl.findAll(FieldDeclaration.class).stream()
+ .filter(f -> f.getParentNode().isPresent() && f.getParentNode().get() == typeDecl)
+ .map(SymbolTable::processFieldDeclaration).collect(Collectors.toList()));
+ List fieldNames = new ArrayList<>();
+ typeNode.getFieldDeclarations().stream().map(Field::getVariables).forEach(fieldNames::addAll);
+
+ // Add information about declared methods (filtering to methods declared in the
+ // class, not in a nested class)
+ typeNode.setCallableDeclarations(typeDecl.findAll(CallableDeclaration.class).stream()
+ .filter(c -> c.getParentNode().isPresent() && c.getParentNode().get() == typeDecl)
+ .map(meth -> {
+ Pair callableDeclaration = processCallableDeclaration(meth,
+ fieldNames, typeName, parseResult.getStorage().map(s -> s.getPath().toString())
+ .orElse(""));
+ declaredMethodsAndConstructors.put(typeName, callableDeclaration.getLeft(),
+ callableDeclaration.getRight());
+ return callableDeclaration;
+ }).collect(Collectors.toMap(p -> p.getLeft(), p -> p.getRight())));
+
+ // Add information about if the TypeNode is an entry point class
+ typeNode.setEntrypointClass(isEntryPointClass(typeDecl));
+
+ return Pair.of(typeName, typeNode);
+
+ }).collect(Collectors.toMap(p -> p.getLeft(), p -> p.getRight())));
return cUnit;
}
- private static InitializationBlock createInitializationBlock(InitializerDeclaration initializerDeclaration, String filePath) {
+ private static InitializationBlock createInitializationBlock(InitializerDeclaration initializerDeclaration,
+ String filePath) {
InitializationBlock initializationBlock = new InitializationBlock();
initializationBlock.setFilePath(filePath);
- initializationBlock.setComment(initializerDeclaration.getComment().isPresent() ? initializerDeclaration.getComment().get().asString() : "");
- initializationBlock.setAnnotations(initializerDeclaration.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
+
+ com.ibm.cldk.entities.Comment comment = new com.ibm.cldk.entities.Comment();
+
+ // Add class comment
+ initializationBlock.setComments(
+ initializerDeclaration.getAllContainedComments().stream()
+ .map(c -> {
+ com.ibm.cldk.entities.Comment typeNodeComment = new com.ibm.cldk.entities.Comment();
+ typeNodeComment.setContent(c.getContent());
+ typeNodeComment.setStartLine(c.getRange().isPresent() ? c.getRange().get().begin.line : -1);
+ typeNodeComment.setEndLine(c.getRange().isPresent() ? c.getRange().get().end.line : -1);
+ typeNodeComment.setStartColumn(c.getRange().isPresent() ? c.getRange().get().begin.column : -1);
+ typeNodeComment.setEndColumn(c.getRange().isPresent() ? c.getRange().get().end.column : -1);
+ typeNodeComment.setJavadoc(c.isJavadocComment());
+ return typeNodeComment;
+ })
+ .collect(Collectors.toList()));
+
+ // Check to see if there is a java doc comment if so, add it to the comments list
+ getJavadoc(initializerDeclaration).ifPresent(value -> initializationBlock.getComments().add(value));
+
+
+ // Set annotations
+ initializationBlock.setAnnotations(initializerDeclaration.getAnnotations().stream()
+ .map(a -> a.toString().strip()).collect(Collectors.toList()));
// add exceptions declared in "throws" clause
- initializationBlock.setThrownExceptions(initializerDeclaration.getBody().getStatements().stream().filter(Statement::isThrowStmt).map(throwStmt -> {
- try {
- return javaSymbolSolver.calculateType(throwStmt.asThrowStmt().getExpression()).describe();
- } catch (Exception e) {
- return throwStmt.asThrowStmt().getExpression().toString();
- }
- }).collect(Collectors.toList()));
+ initializationBlock.setThrownExceptions(initializerDeclaration.getBody().getStatements().stream()
+ .filter(Statement::isThrowStmt).map(throwStmt -> {
+ try {
+ return javaSymbolSolver.calculateType(throwStmt.asThrowStmt().getExpression()).describe();
+ } catch (Exception e) {
+ return throwStmt.asThrowStmt().getExpression().toString();
+ }
+ }).collect(Collectors.toList()));
initializationBlock.setCode(initializerDeclaration.getBody().toString());
- initializationBlock.setStartLine(initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().begin.line : -1);
- initializationBlock.setEndLine(initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().end.line : -1);
+ initializationBlock.setStartLine(
+ initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().begin.line
+ : -1);
+ initializationBlock.setEndLine(
+ initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().end.line : -1);
initializationBlock.setStatic(initializerDeclaration.isStatic());
- initializationBlock.setReferencedTypes(getReferencedTypes(Optional.ofNullable(initializerDeclaration.getBody())));
- initializationBlock.setAccessedFields(getAccessedFields(Optional.ofNullable(initializerDeclaration.getBody()), Collections.emptyList(), ""));
+ initializationBlock
+ .setReferencedTypes(getReferencedTypes(Optional.ofNullable(initializerDeclaration.getBody())));
+ initializationBlock.setAccessedFields(
+ getAccessedFields(Optional.ofNullable(initializerDeclaration.getBody()), Collections.emptyList(), ""));
initializationBlock.setCallSites(getCallSites(Optional.ofNullable(initializerDeclaration.getBody())));
- initializationBlock.setVariableDeclarations(getVariableDeclarations(Optional.ofNullable(initializerDeclaration.getBody())));
+ initializationBlock.setVariableDeclarations(
+ getVariableDeclarations(Optional.ofNullable(initializerDeclaration.getBody())));
initializationBlock.setCyclomaticComplexity(getCyclomaticComplexity(initializerDeclaration));
return initializationBlock;
}
+
+ private static Optional getJavadoc(NodeWithJavadoc bodyDeclaration) {
+ Optional javadocComment = bodyDeclaration.getJavadocComment();
+ if (!javadocComment.isPresent()) {
+ return Optional.empty();
+ }
+ com.ibm.cldk.entities.Comment javadoc = new com.ibm.cldk.entities.Comment();
+ javadoc.setContent(javadocComment.get().getContent().isEmpty() || javadocComment.get().getContent().isBlank() ? "" : javadocComment.get().getContent());
+ javadoc.setStartLine(javadocComment.get().getRange().get().begin.line);
+ javadoc.setEndLine(javadocComment.get().getRange().get().end.line);
+ javadoc.setStartColumn(javadocComment.get().getRange().get().begin.column);
+ javadoc.setEndColumn(javadocComment.get().getRange().get().end.column);
+ javadoc.setJavadoc(!(javadocComment.get().getContent().isEmpty() && javadocComment.get().getContent().isBlank()));
+ return Optional.of(javadoc);
+ }
+
/**
* Processes the given record to extract information about the
* declared field and returns a JSON object containing the extracted
@@ -255,15 +402,37 @@ private static List processRecordComponents(RecordDeclaration r
return recordDecl.getParameters().stream().map(
parameter -> {
RecordComponent recordComponent = new RecordComponent();
+ com.ibm.cldk.entities.Comment comment = new com.ibm.cldk.entities.Comment();
+ if (parameter.getComment().isPresent()) {
+ Comment parsedComment = parameter.getComment().get();
+ comment.setContent(parsedComment.getContent());
+ parsedComment.getRange().ifPresent(range -> {
+ comment.setStartLine(range.begin.line);
+ comment.setEndLine(range.end.line);
+ comment.setStartColumn(range.begin.column);
+ comment.setEndColumn(range.end.column);
+ });
+
+ } else {
+ comment.setContent("");
+ comment.setStartLine(-1);
+ comment.setEndLine(-1);
+ comment.setStartColumn(-1);
+ comment.setEndColumn(-1);
+ }
+
+ recordComponent.setComment(comment);
recordComponent.setName(parameter.getNameAsString());
recordComponent.setType(resolveType(parameter.getType()));
- recordComponent.setAnnotations(parameter.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
- recordComponent.setModifiers(parameter.getModifiers().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
+ recordComponent.setAnnotations(parameter.getAnnotations().stream().map(a -> a.toString().strip())
+ .collect(Collectors.toList()));
+ recordComponent.setModifiers(parameter.getModifiers().stream().map(a -> a.toString().strip())
+ .collect(Collectors.toList()));
recordComponent.setVarArgs(parameter.isVarArgs());
- recordComponent.setDefaultValue(mapRecordConstructorDefaults(recordDecl).getOrDefault(parameter.getNameAsString(), null));
+ recordComponent.setDefaultValue(
+ mapRecordConstructorDefaults(recordDecl).getOrDefault(parameter.getNameAsString(), null));
return recordComponent;
- }
- ).collect(Collectors.toList());
+ }).collect(Collectors.toList());
}
private static Map mapRecordConstructorDefaults(RecordDeclaration recordDecl) {
@@ -273,19 +442,26 @@ private static Map mapRecordConstructorDefaults(RecordDeclaratio
.filter(assignExpr -> assignExpr.getTarget().isNameExpr()) // Ensure assignment is to a parameter
.collect(Collectors.toMap(
assignExpr -> assignExpr.getTarget().asNameExpr().getNameAsString(), // Key: Parameter Name
- assignExpr -> Optional.ofNullable(assignExpr.getValue()).map(valueExpr -> { // Value: Default Value
+ assignExpr -> Optional.ofNullable(assignExpr.getValue()).map(valueExpr -> { // Value: Default
+ // Value
return valueExpr.isStringLiteralExpr() ? valueExpr.asStringLiteralExpr().asString()
: valueExpr.isBooleanLiteralExpr() ? valueExpr.asBooleanLiteralExpr().getValue()
- : valueExpr.isCharLiteralExpr() ? valueExpr.asCharLiteralExpr().getValue()
- : valueExpr.isDoubleLiteralExpr() ? valueExpr.asDoubleLiteralExpr().asDouble()
- : valueExpr.isIntegerLiteralExpr() ? valueExpr.asIntegerLiteralExpr().asNumber()
- : valueExpr.isLongLiteralExpr() ? valueExpr.asLongLiteralExpr().asNumber()
- : valueExpr.isNullLiteralExpr() ? null
- : valueExpr.toString();}).orElse("null"))); // Default: store as a string
+ : valueExpr.isCharLiteralExpr() ? valueExpr.asCharLiteralExpr().getValue()
+ : valueExpr.isDoubleLiteralExpr()
+ ? valueExpr.asDoubleLiteralExpr().asDouble()
+ : valueExpr.isIntegerLiteralExpr()
+ ? valueExpr.asIntegerLiteralExpr().asNumber()
+ : valueExpr.isLongLiteralExpr()
+ ? valueExpr.asLongLiteralExpr().asNumber()
+ : valueExpr.isNullLiteralExpr() ? null
+ : valueExpr.toString();
+ }).orElse("null"))); // Default: store as a string
}
private static boolean isEntryPointClass(TypeDeclaration typeDecl) {
- return isSpringEntrypointClass(typeDecl) || isStrutsEntryPointClass(typeDecl) || isCamelEntryPointClass(typeDecl) || isJaxRSEntrypointClass(typeDecl) || isJakartaServletEntryPointClass(typeDecl);
+ return isSpringEntrypointClass(typeDecl) || isStrutsEntryPointClass(typeDecl)
+ || isCamelEntryPointClass(typeDecl) || isJaxRSEntrypointClass(typeDecl)
+ || isJakartaServletEntryPointClass(typeDecl);
}
@@ -293,12 +469,19 @@ private static boolean isSpringEntrypointClass(TypeDeclaration typeDeclaration)
List annotations = typeDeclaration.getAnnotations();
for (AnnotationExpr annotation : annotations) {
// Existing checks
- if (annotation.getNameAsString().contains("RestController") || annotation.getNameAsString().contains("Controller") || annotation.getNameAsString().contains("HandleInterceptor") || annotation.getNameAsString().contains("HandlerInterceptor")) {
+ if (annotation.getNameAsString().contains("RestController")
+ || annotation.getNameAsString().contains("Controller")
+ || annotation.getNameAsString().contains("HandleInterceptor")
+ || annotation.getNameAsString().contains("HandlerInterceptor")) {
return true;
}
// Spring Boot specific checks
- if (annotation.getNameAsString().contains("SpringBootApplication") || annotation.getNameAsString().contains("Configuration") || annotation.getNameAsString().contains("Component") || annotation.getNameAsString().contains("Service") || annotation.getNameAsString().contains("Repository")) {
+ if (annotation.getNameAsString().contains("SpringBootApplication")
+ || annotation.getNameAsString().contains("Configuration")
+ || annotation.getNameAsString().contains("Component")
+ || annotation.getNameAsString().contains("Service")
+ || annotation.getNameAsString().contains("Repository")) {
return true;
}
}
@@ -320,7 +503,11 @@ private static boolean isSpringEntrypointClass(TypeDeclaration typeDeclaration)
private static boolean isJaxRSEntrypointClass(TypeDeclaration typeDeclaration) {
List callableDeclarations = typeDeclaration.findAll(CallableDeclaration.class);
for (CallableDeclaration callableDeclaration : callableDeclarations) {
- if (callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("POST")) || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("PUT")) || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("GET")) || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("HEAD")) || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("DELETE"))) {
+ if (callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("POST"))
+ || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("PUT"))
+ || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("GET"))
+ || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("HEAD"))
+ || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("DELETE"))) {
return true;
}
}
@@ -336,7 +523,8 @@ private static boolean isStrutsEntryPointClass(TypeDeclaration typeDeclaration)
ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration;
// Check class-level Struts annotations
- if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Action") || a.getNameAsString().contains("Namespace") || a.getNameAsString().contains("InterceptorRef"))) {
+ if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Action")
+ || a.getNameAsString().contains("Namespace") || a.getNameAsString().contains("InterceptorRef"))) {
return true;
}
@@ -371,7 +559,8 @@ private static boolean isCamelEntryPointClass(TypeDeclaration typeDeclaration) {
ResolvedReferenceTypeDeclaration resolved = classDecl.resolve();
return resolved.getAllAncestors().stream().anyMatch(ancestor -> {
String name = ancestor.getQualifiedName();
- return name.contains("RouteBuilder") || name.contains("Processor") || name.contains("Producer") || name.contains("Consumer");
+ return name.contains("RouteBuilder") || name.contains("Processor") || name.contains("Producer")
+ || name.contains("Consumer");
});
} catch (UnsolvedSymbolException e) {
Log.warn("Could not resolve class: " + e.getMessage());
@@ -384,7 +573,8 @@ private static boolean isCamelEntryPointClass(TypeDeclaration typeDeclaration) {
* Checks if the given class is a Jakarta Servlet entry point class.
*
* @param typeDecl Type declaration to check
- * @return True if the class is a Jakarta Servlet entry point class, false otherwise
+ * @return True if the class is a Jakarta Servlet entry point class, false
+ * otherwise
*/
private static boolean isJakartaServletEntryPointClass(TypeDeclaration typeDecl) {
if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) {
@@ -394,7 +584,11 @@ private static boolean isJakartaServletEntryPointClass(TypeDeclaration typeDecl)
ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl;
// Check annotations
- if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("WebServlet") || a.getNameAsString().contains("WebFilter") || a.getNameAsString().contains("WebListener") || a.getNameAsString().contains("ServerEndpoint") || a.getNameAsString().contains("MessageDriven") || a.getNameAsString().contains("WebService"))) {
+ if (classDecl.getAnnotations().stream()
+ .anyMatch(a -> a.getNameAsString().contains("WebServlet") || a.getNameAsString().contains("WebFilter")
+ || a.getNameAsString().contains("WebListener") || a.getNameAsString().contains("ServerEndpoint")
+ || a.getNameAsString().contains("MessageDriven")
+ || a.getNameAsString().contains("WebService"))) {
return true;
}
@@ -403,7 +597,8 @@ private static boolean isJakartaServletEntryPointClass(TypeDeclaration typeDecl)
.map(ClassOrInterfaceType::getNameAsString)
.anyMatch(name -> name.contains("HttpServlet") || name.contains("GenericServlet"))
|| classDecl.getImplementedTypes().stream().map(
- ClassOrInterfaceType::asString).anyMatch(name -> name.contains("ServletContextListener")
+ ClassOrInterfaceType::asString).anyMatch(
+ name -> name.contains("ServletContextListener")
|| name.contains("HttpSessionListener")
|| name.contains("ServletRequestListener")
|| name.contains("MessageListener"));
@@ -422,7 +617,8 @@ private static EnumConstant processEnumConstantDeclaration(EnumConstantDeclarati
enumConstant.setName(enumConstDecl.getNameAsString());
// add enum constant arguments
- enumConstant.setArguments(enumConstDecl.getArguments().stream().map(a -> a.toString()).collect(Collectors.toList()));
+ enumConstant.setArguments(
+ enumConstDecl.getArguments().stream().map(Node::toString).collect(Collectors.toList()));
return enumConstant;
}
@@ -436,8 +632,10 @@ private static ParameterInCallable processParameterDeclaration(Parameter paramDe
ParameterInCallable parameter = new ParameterInCallable();
parameter.setType(resolveType(paramDecl.getType()));
parameter.setName(paramDecl.getName().toString());
- parameter.setAnnotations(paramDecl.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
- parameter.setModifiers(paramDecl.getModifiers().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
+ parameter.setAnnotations(
+ paramDecl.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
+ parameter.setModifiers(
+ paramDecl.getModifiers().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
parameter.setStartLine(paramDecl.getRange().isPresent() ? paramDecl.getRange().get().begin.line : -1);
parameter.setStartColumn(paramDecl.getRange().isPresent() ? paramDecl.getRange().get().begin.column : -1);
parameter.setEndLine(paramDecl.getRange().isPresent() ? paramDecl.getRange().get().end.line : -1);
@@ -454,7 +652,8 @@ private static ParameterInCallable processParameterDeclaration(Parameter paramDe
* @return Callable object containing extracted information
*/
@SuppressWarnings("unchecked")
- private static Pair processCallableDeclaration(CallableDeclaration callableDecl, List classFields, String typeName, String filePath) {
+ private static Pair processCallableDeclaration(CallableDeclaration callableDecl,
+ List classFields, String typeName, String filePath) {
Callable callableNode = new Callable();
// Set file path
@@ -464,37 +663,60 @@ private static Pair processCallableDeclaration(CallableDeclara
callableNode.setSignature(callableDecl.getSignature().asString());
// add comment associated with method/constructor
- callableNode.setComment(callableDecl.getComment().isPresent() ? callableDecl.getComment().get().asString() : "");
+ callableNode.setComments(
+ callableDecl.getAllContainedComments().stream()
+ .map(c -> {
+ com.ibm.cldk.entities.Comment methodComment = new com.ibm.cldk.entities.Comment();
+ methodComment.setContent(c.getContent());
+ methodComment.setStartLine(c.getRange().isPresent() ? c.getRange().get().begin.line : -1);
+ methodComment.setEndLine(c.getRange().isPresent() ? c.getRange().get().end.line : -1);
+ methodComment.setStartColumn(c.getRange().isPresent() ? c.getRange().get().begin.column : -1);
+ methodComment.setEndColumn(c.getRange().isPresent() ? c.getRange().get().end.column : -1);
+ methodComment.setJavadoc(c.isJavadocComment());
+ return methodComment;
+ })
+ .collect(Collectors.toList()));
+
+ // Check to see if there are JavaDoc comments
+ getJavadoc(callableDecl).ifPresent(value -> callableNode.getComments().add(value));
// add annotations on method/constructor
- callableNode.setAnnotations((List) callableDecl.getAnnotations().stream().map(mod -> mod.toString().strip()).collect(Collectors.toList()));
+ callableNode.setAnnotations((List) callableDecl.getAnnotations().stream()
+ .map(mod -> mod.toString().strip()).collect(Collectors.toList()));
// add method or constructor modifiers
- callableNode.setModifiers((List) callableDecl.getModifiers().stream().map(mod -> mod.toString().strip()).collect(Collectors.toList()));
+ callableNode.setModifiers((List) callableDecl.getModifiers().stream().map(mod -> mod.toString().strip())
+ .collect(Collectors.toList()));
// add exceptions declared in "throws" clause
- callableNode.setThrownExceptions(((NodeList) callableDecl.getThrownExceptions()).stream().map(SymbolTable::resolveType).collect(Collectors.toList()));
+ callableNode.setThrownExceptions(((NodeList) callableDecl.getThrownExceptions()).stream()
+ .map(SymbolTable::resolveType).collect(Collectors.toList()));
// add the complete declaration string, including modifiers, throws, and
// parameter names
- callableNode.setDeclaration(callableDecl.getDeclarationAsString(true, true, true).strip().replaceAll("//.*\n", ""));
+ callableNode
+ .setDeclaration(callableDecl.getDeclarationAsString(true, true, true).strip().replaceAll("//.*\n", ""));
// add information about callable parameters: for each parameter, type, name,
// annotations,
// modifiers
- callableNode.setParameters((List) callableDecl.getParameters().stream().map(param -> processParameterDeclaration((Parameter) param)).collect(Collectors.toList()));
+ callableNode.setParameters((List) callableDecl.getParameters().stream()
+ .map(param -> processParameterDeclaration((Parameter) param)).collect(Collectors.toList()));
callableNode.setEntrypoint(isEntryPointMethod(callableDecl));
+
// A method declaration may not have a body if it is an abstract method. A
- // constructor always
- // has a body. So, we need to check if the body is present before processing it
- // and capture it
- // using the Optional type.
- Optional body = (callableDecl instanceof MethodDeclaration) ? ((MethodDeclaration) callableDecl).getBody() : Optional.ofNullable(((ConstructorDeclaration) callableDecl).getBody());
+ // constructor always has a body. So, we need to check if the body is present before processing it
+ // and capture it using the Optional type.
+ Optional body = (callableDecl instanceof MethodDeclaration)
+ ? ((MethodDeclaration) callableDecl).getBody()
+ : Optional.ofNullable(((ConstructorDeclaration) callableDecl).getBody());
// Same as above, a constructor declaration may not have a return type
// and method declaration always has a return type.
- callableNode.setReturnType((callableDecl instanceof MethodDeclaration) ? resolveType(((MethodDeclaration) callableDecl).getType()) : null);
+ callableNode.setReturnType(
+ (callableDecl instanceof MethodDeclaration) ? resolveType(((MethodDeclaration) callableDecl).getType())
+ : null);
callableNode.setConstructor(callableDecl instanceof ConstructorDeclaration);
callableNode.setStartLine(callableDecl.getRange().isPresent() ? callableDecl.getRange().get().begin.line : -1);
@@ -508,41 +730,44 @@ private static Pair processCallableDeclaration(CallableDeclara
callableNode.getCallSites().stream()
.map(CallSite::getCrudOperation)
.filter(Objects::nonNull)
- .collect(Collectors.toList())
- );
+ .collect(Collectors.toList()));
callableNode.setCrudQueries(
callableNode.getCallSites().stream()
.map(CallSite::getCrudQuery)
.filter(Objects::nonNull)
- .collect(Collectors.toList())
- );
+ .collect(Collectors.toList()));
callableNode.setVariableDeclarations(getVariableDeclarations(body));
callableNode.setCyclomaticComplexity(getCyclomaticComplexity(callableDecl));
- String callableSignature = (callableDecl instanceof MethodDeclaration) ? callableDecl.getSignature().asString() : callableDecl.getSignature().asString().replace(callableDecl.getSignature().getName(), "");
+ String callableSignature = (callableDecl instanceof MethodDeclaration) ? callableDecl.getSignature().asString()
+ : callableDecl.getSignature().asString().replace(callableDecl.getSignature().getName(), "");
return Pair.of(callableSignature, callableNode);
}
private static boolean isEntryPointMethod(CallableDeclaration callableDecl) {
- return isServletEntrypointMethod(callableDecl) || isJaxRsEntrypointMethod(callableDecl) || isSpringEntrypointMethod(callableDecl) | isStrutsEntryPointMethod(callableDecl);
+ return isServletEntrypointMethod(callableDecl) || isJaxRsEntrypointMethod(callableDecl)
+ || isSpringEntrypointMethod(callableDecl) | isStrutsEntryPointMethod(callableDecl);
}
@SuppressWarnings("unchecked")
private static boolean isServletEntrypointMethod(CallableDeclaration callableDecl) {
return ((NodeList) callableDecl.getParameters()).stream()
.anyMatch(parameter -> parameter.getType().asString().contains("HttpServletRequest") ||
- parameter.getType().asString().contains("HttpServletResponse")) && callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Override"));
+ parameter.getType().asString().contains("HttpServletResponse"))
+ && callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Override"));
}
@SuppressWarnings("unchecked")
private static boolean isJaxRsEntrypointMethod(CallableDeclaration callableDecl) {
- return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("POST") || a.toString().contains("PUT") || a.toString().contains("GET") || a.toString().contains("HEAD") || a.toString().contains("DELETE"));
+ return callableDecl.getAnnotations().stream()
+ .anyMatch(a -> a.toString().contains("POST") || a.toString().contains("PUT")
+ || a.toString().contains("GET") || a.toString().contains("HEAD")
+ || a.toString().contains("DELETE"));
}
@SuppressWarnings("unchecked")
private static boolean isSpringEntrypointMethod(CallableDeclaration callableDecl) {
- return callableDecl.getAnnotations().stream().anyMatch(a ->
- a.toString().contains("GetMapping") ||
+ return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("GetMapping") ||
a.toString().contains("PostMapping") ||
a.toString().contains("PutMapping") ||
a.toString().contains("DeleteMapping") ||
@@ -561,8 +786,7 @@ private static boolean isSpringEntrypointMethod(CallableDeclaration callableDecl
a.toString().contains("Before") ||
a.toString().contains("After") ||
a.toString().contains("JobScope") ||
- a.toString().contains("StepScope")
- );
+ a.toString().contains("StepScope"));
}
@SuppressWarnings("unchecked")
@@ -579,18 +803,23 @@ private static boolean isStrutsEntryPointMethod(CallableDeclaration callableDecl
.noneMatch(type -> type.contains("ActionSupport") || type.contains("Action")))
return false;
- return callableDecl.getAnnotations().stream().anyMatch(a ->
- a.toString().contains("Action") ||
- a.toString().contains("Actions") ||
- a.toString().contains("ValidationMethod") ||
- a.toString().contains("InputConfig") ||
- a.toString().contains("BeforeResult") ||
- a.toString().contains("After") ||
- a.toString().contains("Before") ||
- a.toString().contains("Result") ||
- a.toString().contains("Results")
- ) || callableDecl.getNameAsString().equals("execute"); // Check for execute() method which is the default action method of the Action class
+ return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Action") ||
+ a.toString().contains("Actions") ||
+ a.toString().contains("ValidationMethod") ||
+ a.toString().contains("InputConfig") ||
+ a.toString().contains("BeforeResult") ||
+ a.toString().contains("After") ||
+ a.toString().contains("Before") ||
+ a.toString().contains("Result") ||
+ a.toString().contains("Results")) || callableDecl.getNameAsString().equals("execute"); // Check for
+ // execute()
+ // method which
+ // is the default
+ // action method
+ // of the Action
+ // class
}
+
/**
* Computes cyclomatic complexity for the given callable.
*
@@ -599,16 +828,25 @@ private static boolean isStrutsEntryPointMethod(CallableDeclaration callableDecl
*/
private static int getCyclomaticComplexity(CallableDeclaration callableDeclaration) {
int ifStmtCount = callableDeclaration.findAll(IfStmt.class).size();
- int loopStmtCount = callableDeclaration.findAll(DoStmt.class).size() + callableDeclaration.findAll(ForStmt.class).size() + callableDeclaration.findAll(ForEachStmt.class).size() + callableDeclaration.findAll(WhileStmt.class).size();
- int switchCaseCount = callableDeclaration.findAll(SwitchStmt.class).stream().map(stmt -> stmt.getEntries().size()).reduce(0, Integer::sum);
+ int loopStmtCount = callableDeclaration.findAll(DoStmt.class).size()
+ + callableDeclaration.findAll(ForStmt.class).size()
+ + callableDeclaration.findAll(ForEachStmt.class).size()
+ + callableDeclaration.findAll(WhileStmt.class).size();
+ int switchCaseCount = callableDeclaration.findAll(SwitchStmt.class).stream()
+ .map(stmt -> stmt.getEntries().size()).reduce(0, Integer::sum);
int conditionalExprCount = callableDeclaration.findAll(ConditionalExpr.class).size();
int catchClauseCount = callableDeclaration.findAll(CatchClause.class).size();
return ifStmtCount + loopStmtCount + switchCaseCount + conditionalExprCount + catchClauseCount + 1;
}
+
private static int getCyclomaticComplexity(InitializerDeclaration initializerDeclaration) {
int ifStmtCount = initializerDeclaration.findAll(IfStmt.class).size();
- int loopStmtCount = initializerDeclaration.findAll(DoStmt.class).size() + initializerDeclaration.findAll(ForStmt.class).size() + initializerDeclaration.findAll(ForEachStmt.class).size() + initializerDeclaration.findAll(WhileStmt.class).size();
- int switchCaseCount = initializerDeclaration.findAll(SwitchStmt.class).stream().map(stmt -> stmt.getEntries().size()).reduce(0, Integer::sum);
+ int loopStmtCount = initializerDeclaration.findAll(DoStmt.class).size()
+ + initializerDeclaration.findAll(ForStmt.class).size()
+ + initializerDeclaration.findAll(ForEachStmt.class).size()
+ + initializerDeclaration.findAll(WhileStmt.class).size();
+ int switchCaseCount = initializerDeclaration.findAll(SwitchStmt.class).stream()
+ .map(stmt -> stmt.getEntries().size()).reduce(0, Integer::sum);
int conditionalExprCount = initializerDeclaration.findAll(ConditionalExpr.class).size();
int catchClauseCount = initializerDeclaration.findAll(CatchClause.class).size();
return ifStmtCount + loopStmtCount + switchCaseCount + conditionalExprCount + catchClauseCount + 1;
@@ -626,16 +864,30 @@ private static Field processFieldDeclaration(FieldDeclaration fieldDecl) {
Field field = new Field();
// add comment associated with field
- field.setComment(fieldDecl.getComment().isPresent() ? fieldDecl.getComment().get().asString() : "");
+ com.ibm.cldk.entities.Comment comment = new com.ibm.cldk.entities.Comment();
+ if (fieldDecl.getComment().isPresent()) {
+ Comment parsedComment = fieldDecl.getComment().get();
+ comment.setContent(parsedComment.getContent());
+ parsedComment.getRange().ifPresent(range -> {
+ comment.setStartLine(range.begin.line);
+ comment.setEndLine(range.end.line);
+ comment.setStartColumn(range.begin.column);
+ comment.setEndColumn(range.end.column);
+ });
+ }
+ field.setComment(comment);
// add annotations on field
- field.setAnnotations(fieldDecl.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
+ field.setAnnotations(
+ fieldDecl.getAnnotations().stream().map(a -> a.toString().strip()).collect(Collectors.toList()));
// add variable names
- field.setVariables(fieldDecl.getVariables().stream().map(v -> v.getName().asString()).collect(Collectors.toList()));
+ field.setVariables(
+ fieldDecl.getVariables().stream().map(v -> v.getName().asString()).collect(Collectors.toList()));
// add field modifiers
- field.setModifiers(fieldDecl.getModifiers().stream().map(m -> m.toString().strip()).collect(Collectors.toList()));
+ field.setModifiers(
+ fieldDecl.getModifiers().stream().map(m -> m.toString().strip()).collect(Collectors.toList()));
// add field type
field.setType(resolveType(fieldDecl.getCommonType()));
@@ -657,16 +909,21 @@ private static Field processFieldDeclaration(FieldDeclaration fieldDecl) {
*/
private static List getReferencedTypes(Optional blockStmt) {
Set referencedTypes = new HashSet<>();
- blockStmt.ifPresent(bs -> bs.findAll(VariableDeclarator.class).stream().filter(vd -> vd.getType().isClassOrInterfaceType()).map(vd -> resolveType(vd.getType())).forEach(referencedTypes::add));
+ blockStmt.ifPresent(
+ bs -> bs.findAll(VariableDeclarator.class).stream().filter(vd -> vd.getType().isClassOrInterfaceType())
+ .map(vd -> resolveType(vd.getType())).forEach(referencedTypes::add));
// add types of accessed fields to the set of referenced types
- blockStmt.ifPresent(bs -> bs.findAll(FieldAccessExpr.class).stream().filter(faExpr -> faExpr.getParentNode().isPresent() && !(faExpr.getParentNode().get() instanceof FieldAccessExpr)).map(faExpr -> {
- if (faExpr.getParentNode().isPresent() && faExpr.getParentNode().get() instanceof CastExpr) {
- return resolveType(((CastExpr) faExpr.getParentNode().get()).getType());
- } else {
- return resolveExpression(faExpr);
- }
- }).filter(type -> !type.isEmpty()).forEach(referencedTypes::add));
+ blockStmt.ifPresent(
+ bs -> bs.findAll(FieldAccessExpr.class).stream().filter(faExpr -> faExpr.getParentNode().isPresent()
+ && !(faExpr.getParentNode().get() instanceof FieldAccessExpr)).map(faExpr -> {
+ if (faExpr.getParentNode().isPresent()
+ && faExpr.getParentNode().get() instanceof CastExpr) {
+ return resolveType(((CastExpr) faExpr.getParentNode().get()).getType());
+ } else {
+ return resolveExpression(faExpr);
+ }
+ }).filter(type -> !type.isEmpty()).forEach(referencedTypes::add));
// TODO: add resolved method access expressions
return new ArrayList<>(referencedTypes);
@@ -687,9 +944,22 @@ private static List getVariableDeclarations(Optional {
+ comment.setStartLine(range.begin.line);
+ comment.setEndLine(range.end.line);
+ comment.setStartColumn(range.begin.column);
+ comment.setEndColumn(range.end.column);
+ });
+ }
+ varDeclaration.setComment(comment);
varDeclaration.setName(declarator.getNameAsString());
varDeclaration.setType(resolveType(declarator.getType()));
- varDeclaration.setInitializer(declarator.getInitializer().isPresent() ? declarator.getInitializer().get().toString() : "");
+ varDeclaration.setInitializer(
+ declarator.getInitializer().isPresent() ? declarator.getInitializer().get().toString() : "");
if (declarator.getRange().isPresent()) {
varDeclaration.setStartLine(declarator.getRange().get().begin.line);
varDeclaration.setStartColumn(declarator.getRange().get().begin.column);
@@ -714,20 +984,24 @@ private static List getVariableDeclarations(Optional getAccessedFields(Optional callableBody, List classFields, String typeName) {
+ private static List getAccessedFields(Optional callableBody, List classFields,
+ String typeName) {
Set accessedFields = new HashSet<>();
// process field access expressions in the callable
- callableBody.ifPresent(cb -> cb.findAll(FieldAccessExpr.class).stream().filter(faExpr -> faExpr.getParentNode().isPresent() && !(faExpr.getParentNode().get() instanceof FieldAccessExpr)).map(faExpr -> {
- String fieldDeclaringType = resolveExpression(faExpr.getScope());
- if (!fieldDeclaringType.isEmpty()) {
- return fieldDeclaringType + "." + faExpr.getNameAsString();
- } else {
- return faExpr.getNameAsString();
- }
- }).forEach(accessedFields::add));
-
- // process all names expressions in callable and match against names of declared fields
+ callableBody.ifPresent(
+ cb -> cb.findAll(FieldAccessExpr.class).stream().filter(faExpr -> faExpr.getParentNode().isPresent()
+ && !(faExpr.getParentNode().get() instanceof FieldAccessExpr)).map(faExpr -> {
+ String fieldDeclaringType = resolveExpression(faExpr.getScope());
+ if (!fieldDeclaringType.isEmpty()) {
+ return fieldDeclaringType + "." + faExpr.getNameAsString();
+ } else {
+ return faExpr.getNameAsString();
+ }
+ }).forEach(accessedFields::add));
+
+ // process all names expressions in callable and match against names of declared
+ // fields
// in class TODO: handle local variable declarations with the same name
if (callableBody.isPresent()) {
for (NameExpr nameExpr : callableBody.get().findAll(NameExpr.class)) {
@@ -750,7 +1024,7 @@ private static List getAccessedFields(Optional callableBody,
* @param callableBody callable to compute call-site information for
* @return list of call sites
*/
- @SuppressWarnings({"OptionalUsedAsFieldOrParameterType"})
+ @SuppressWarnings({ "OptionalUsedAsFieldOrParameterType" })
private static List getCallSites(Optional callableBody) {
List callSites = new ArrayList<>();
if (callableBody.isEmpty()) {
@@ -769,14 +1043,18 @@ private static List getCallSites(Optional callableBody) {
if (declaringType.contains(" | ")) {
declaringType = declaringType.split(" \\| ")[0];
}
- String declaringTypeName = declaringType.contains(".") ? declaringType.substring(declaringType.lastIndexOf(".") + 1) : declaringType;
+ String declaringTypeName = declaringType.contains(".")
+ ? declaringType.substring(declaringType.lastIndexOf(".") + 1)
+ : declaringType;
if (declaringTypeName.equals(scopeExpr.toString())) {
isStaticCall = true;
}
}
- // compute return type for method call taking into account typecast of return value
- if (methodCallExpr.getParentNode().isPresent() && methodCallExpr.getParentNode().get() instanceof CastExpr) {
+ // compute return type for method call taking into account typecast of return
+ // value
+ if (methodCallExpr.getParentNode().isPresent()
+ && methodCallExpr.getParentNode().get() instanceof CastExpr) {
returnType = resolveType(((CastExpr) methodCallExpr.getParentNode().get()).getType());
} else {
returnType = resolveExpression(methodCallExpr);
@@ -796,35 +1074,48 @@ private static List getCallSites(Optional callableBody) {
ResolvedMethodDeclaration resolvedMethodDeclaration = methodCallExpr.resolve();
accessSpecifier = resolvedMethodDeclaration.accessSpecifier();
} catch (RuntimeException exception) {
- Log.debug("Could not resolve access specifier for method call: " + methodCallExpr + ": " + exception.getMessage());
+ Log.debug("Could not resolve access specifier for method call: " + methodCallExpr + ": "
+ + exception.getMessage());
}
// resolve arguments of the method call to types
- List arguments = methodCallExpr.getArguments().stream().map(SymbolTable::resolveExpression).collect(Collectors.toList());
+ List arguments = methodCallExpr.getArguments().stream().map(SymbolTable::resolveExpression)
+ .collect(Collectors.toList());
// Get argument string from the callsite
- List listOfArgumentStrings = methodCallExpr.getArguments().stream().map(Expression::toString).collect(Collectors.toList());
+ List listOfArgumentStrings = methodCallExpr.getArguments().stream().map(Expression::toString)
+ .collect(Collectors.toList());
// Determine if this call site is potentially a CRUD operation.
CRUDOperation crudOperation = null;
- Optional crudOperationType = findCRUDOperation(declaringType, methodCallExpr.getNameAsString());
+ Optional crudOperationType = findCRUDOperation(declaringType,
+ methodCallExpr.getNameAsString());
if (crudOperationType.isPresent()) {
- // We found a CRUD operation, so we need to populate the details of the call site this CRUD operation.
- int lineNumber = methodCallExpr.getRange().isPresent() ? methodCallExpr.getRange().get().begin.line : -1;
+ // We found a CRUD operation, so we need to populate the details of the call
+ // site this CRUD operation.
+ int lineNumber = methodCallExpr.getRange().isPresent() ? methodCallExpr.getRange().get().begin.line
+ : -1;
crudOperation = new CRUDOperation();
crudOperation.setLineNumber(lineNumber);
crudOperation.setOperationType(crudOperationType.get());
}
// Determine if this call site is potentially a CRUD query.
CRUDQuery crudQuery = null;
- Optional crudQueryType = findCRUDQuery(declaringType, methodCallExpr.getNameAsString(), Optional.of(listOfArgumentStrings));
+ Optional crudQueryType = findCRUDQuery(declaringType, methodCallExpr.getNameAsString(),
+ Optional.of(listOfArgumentStrings));
if (crudQueryType.isPresent()) {
- // We found a CRUD query, so we need to populate the details of the call site this CRUD query.
- int lineNumber = methodCallExpr.getRange().isPresent() ? methodCallExpr.getRange().get().begin.line : -1;
+ // We found a CRUD query, so we need to populate the details of the call site
+ // this CRUD query.
+ int lineNumber = methodCallExpr.getRange().isPresent() ? methodCallExpr.getRange().get().begin.line
+ : -1;
crudQuery = new CRUDQuery();
crudQuery.setLineNumber(lineNumber);
crudQuery.setQueryType(crudQueryType.get());
crudQuery.setQueryArguments(listOfArgumentStrings);
}
// add a new call site object
- callSites.add(createCallSite(methodCallExpr, methodCallExpr.getNameAsString(), receiverName, declaringType, arguments, returnType, calleeSignature, isStaticCall, false, crudOperation, crudQuery, accessSpecifier));
+
+
+ callSites.add(createCallSite(methodCallExpr, methodCallExpr.getNameAsString(), receiverName, declaringType,
+ arguments, returnType, calleeSignature, isStaticCall, false, crudOperation, crudQuery,
+ accessSpecifier));
}
for (ObjectCreationExpr objectCreationExpr : callableBody.get().findAll(ObjectCreationExpr.class)) {
@@ -832,7 +1123,8 @@ private static List getCallSites(Optional callableBody) {
String instantiatedType = resolveType(objectCreationExpr.getType());
// resolve arguments of the constructor call to types
- List arguments = objectCreationExpr.getArguments().stream().map(SymbolTable::resolveExpression).collect(Collectors.toList());
+ List arguments = objectCreationExpr.getArguments().stream().map(SymbolTable::resolveExpression)
+ .collect(Collectors.toList());
// resolve callee and get signature
String calleeSignature = "";
@@ -843,25 +1135,29 @@ private static List getCallSites(Optional callableBody) {
}
// add a new call site object
- callSites.add(createCallSite(objectCreationExpr, "", objectCreationExpr.getScope().isPresent() ? objectCreationExpr.getScope().get().toString() : "", instantiatedType, arguments, instantiatedType, calleeSignature, false, true, null, null, AccessSpecifier.NONE));
+ callSites
+ .add(createCallSite(objectCreationExpr, "",
+ objectCreationExpr.getScope().isPresent() ? objectCreationExpr.getScope().get().toString()
+ : "",
+ instantiatedType, arguments, instantiatedType, calleeSignature, false, true, null, null,
+ AccessSpecifier.NONE));
}
return callSites;
}
-@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
- private static Optional findCRUDQuery(String declaringType, String nameAsString, Optional> arguments) {
+
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+ private static Optional findCRUDQuery(String declaringType, String nameAsString,
+ Optional> arguments) {
return CRUDFinderFactory.getCRUDFinders().map(
finder -> {
if (finder.isReadQuery(declaringType, nameAsString, arguments)) {
return CRUDQueryType.READ;
- }
- else if (finder.isWriteQuery(declaringType, nameAsString, arguments)) {
+ } else if (finder.isWriteQuery(declaringType, nameAsString, arguments)) {
return CRUDQueryType.WRITE;
- }
- else if (finder.isNamedQuery(declaringType, nameAsString, arguments)) {
+ } else if (finder.isNamedQuery(declaringType, nameAsString, arguments)) {
return CRUDQueryType.NAMED;
- }
- else
+ } else
return null;
})
.filter(Objects::nonNull)
@@ -873,17 +1169,13 @@ private static Optional findCRUDOperation(String declaringTyp
finder -> {
if (finder.isCreateOperation(declaringType, nameAsString)) {
return CRUDOperationType.CREATE;
- }
- else if (finder.isReadOperation(declaringType, nameAsString)) {
+ } else if (finder.isReadOperation(declaringType, nameAsString)) {
return CRUDOperationType.READ;
- }
- else if (finder.isUpdateOperation(declaringType, nameAsString)) {
+ } else if (finder.isUpdateOperation(declaringType, nameAsString)) {
return CRUDOperationType.UPDATE;
- }
- else if (finder.isDeleteOperation(declaringType, nameAsString)) {
+ } else if (finder.isDeleteOperation(declaringType, nameAsString)) {
return CRUDOperationType.DELETE;
- }
- else
+ } else
return null;
})
.filter(Objects::nonNull)
@@ -915,9 +1207,22 @@ private static CallSite createCallSite(
boolean isConstructorCall,
CRUDOperation crudOperation,
CRUDQuery crudQuery,
- AccessSpecifier accessSpecifier
- ) {
+ AccessSpecifier accessSpecifier) {
CallSite callSite = new CallSite();
+
+ com.ibm.cldk.entities.Comment comment = new com.ibm.cldk.entities.Comment();
+ callExpr.findAncestor(Node.class).ifPresent(stmt -> {
+ stmt.getComment().ifPresent(c -> {
+ comment.setContent(c.getContent());
+ c.getRange().ifPresent(range -> {
+ comment.setStartLine(range.begin.line);
+ comment.setEndLine(range.end.line);
+ comment.setStartColumn(range.begin.column);
+ comment.setEndColumn(range.end.column);
+ });
+ callSite.setComment(comment);
+ });
+ });
callSite.setMethodName(calleeName);
callSite.setReceiverExpr(receiverExpr);
callSite.setReceiverType(receiverType);
@@ -954,7 +1259,8 @@ private static CallSite createCallSite(
* @return Resolved type name or empty string if type resolution fails
*/
private static String resolveExpression(Expression expression) {
- // perform expression resolution if resolution of this expression did not fail previously
+ // perform expression resolution if resolution of this expression did not fail
+ // previously
if (!unresolvedExpressions.contains(expression.toString())) {
try {
ResolvedType resolvedType = javaSymbolSolver.calculateType(expression);
@@ -998,20 +1304,23 @@ private static String resolveType(Type type) {
*
* @param projectRootPath root path of the project to be analyzed
* @return Pair of extracted symbol table map and parse problems map for
- * project
+ * project
* @throws IOException
*/
- public static Pair
+ *
+ * @author Rahul Krishna
+ * @version 2.3.0
+ */
@Data
public class Callable {
+ /** The file path where the callable entity is defined. */
private String filePath;
+
+ /** The signature of the callable entity. */
private String signature;
- private String comment;
+
+ /** A list of comments associated with the callable entity. */
+ private List comments;
+
+ /** A list of annotations applied to the callable entity. */
private List annotations;
+
+ /** A list of modifiers applied to the callable entity (e.g., public, private). */
private List modifiers;
+
+ /** A list of exceptions thrown by the callable entity. */
private List thrownExceptions;
+
+ /** The declaration of the callable entity. */
private String declaration;
+
+ /** A list of parameters for the callable entity. */
private List parameters;
+
+ /** The code of the callable entity. */
private String code;
+
+ /** The starting line number of the callable entity in the source file. */
private int startLine;
+
+ /** The ending line number of the callable entity in the source file. */
private int endLine;
+
+ /** The return type of the callable entity. */
private String returnType = null;
+
+ /** Indicates whether the callable entity is implicit. */
private boolean isImplicit = false;
+
+ /** Indicates whether the callable entity is a constructor. */
private boolean isConstructor = false;
+
+ /** A list of types referenced by the callable entity. */
private List referencedTypes;
+
+ /** A list of fields accessed by the callable entity. */
private List accessedFields;
+
+ /** A list of call sites within the callable entity. */
private List callSites;
+
+ /** A list of variable declarations within the callable entity. */
private List variableDeclarations;
+
+ /** A list of CRUD operations associated with the callable entity. */
private List crudOperations = new ArrayList<>();
+
+ /** A list of CRUD queries associated with the callable entity. */
private List crudQueries = new ArrayList<>();
+
+ /** The cyclomatic complexity of the callable entity. */
private int cyclomaticComplexity;
+
+ /** Indicates whether the callable entity is an entry point. */
private boolean isEntrypoint = false;
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/ibm/cldk/entities/Field.java b/src/main/java/com/ibm/cldk/entities/Field.java
index 40a8bba9..c8cc0cc5 100644
--- a/src/main/java/com/ibm/cldk/entities/Field.java
+++ b/src/main/java/com/ibm/cldk/entities/Field.java
@@ -5,7 +5,7 @@
@Data
public class Field {
- private String comment;
+ private Comment comment;
private String name;
private String type;
private Integer startLine;
diff --git a/src/main/java/com/ibm/cldk/entities/InitializationBlock.java b/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
index eab76d2b..d10e1f56 100644
--- a/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
+++ b/src/main/java/com/ibm/cldk/entities/InitializationBlock.java
@@ -8,7 +8,7 @@
@Data
public class InitializationBlock {
private String filePath;
- private String comment;
+ private List comments;
private List annotations;
private List thrownExceptions;
private String code;
diff --git a/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java b/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java
index 2ed0095e..2f560c9c 100644
--- a/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java
+++ b/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java
@@ -7,7 +7,8 @@
@Data
public class JavaCompilationUnit {
private String filePath;
- private String comment;
+ private String packageName;
+ private List comments;
private List imports;
private Map typeDeclarations;
private boolean isModified;
diff --git a/src/main/java/com/ibm/cldk/entities/ParameterInCallable.java b/src/main/java/com/ibm/cldk/entities/ParameterInCallable.java
index 85cdc321..985c0c1f 100644
--- a/src/main/java/com/ibm/cldk/entities/ParameterInCallable.java
+++ b/src/main/java/com/ibm/cldk/entities/ParameterInCallable.java
@@ -4,14 +4,60 @@
import java.util.List;
+/**
+ * Represents a parameter in a callable entity (e.g., method or constructor).
+ *
+ *
+ * This class encapsulates information about the parameter's type, name, annotations,
+ * modifiers, and its position within the source file.
+ *
+ *
+ *
+ * This class leverages Lombok's {@code @Data} annotation to automatically generate
+ * getters, setters, {@code toString()}, {@code equals()}, and {@code hashCode()} methods.
+ *
+ *
+ *
+ * Example usage:
+ *
+ * ParameterInCallable param = new ParameterInCallable();
+ * param.setType("String");
+ * param.setName("exampleParam");
+ * param.setAnnotations(Arrays.asList("NotNull"));
+ * param.setModifiers(Arrays.asList("final"));
+ * param.setStartLine(10);
+ * param.setEndLine(10);
+ * param.setStartColumn(5);
+ * param.setEndColumn(20);
+ *
+ *
+ *
+ * @author Rahul Krishna
+ * @version 2.3.0
+ */
@Data
public class ParameterInCallable {
+ /** The type of the parameter (e.g., int, String). */
private String type;
+
+ /** The name of the parameter. */
private String name;
+
+ /** A list of annotations applied to the parameter. */
private List annotations;
+
+ /** A list of modifiers applied to the parameter (e.g., final, static). */
private List modifiers;
+
+ /** The starting line number of the parameter in the source file. */
private int startLine;
+
+ /** The ending line number of the parameter in the source file. */
private int endLine;
+
+ /** The starting column number of the parameter in the source file. */
private int startColumn;
+
+ /** The ending column number of the parameter in the source file. */
private int endColumn;
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/ibm/cldk/entities/RecordComponent.java b/src/main/java/com/ibm/cldk/entities/RecordComponent.java
index 586da61c..814d711e 100644
--- a/src/main/java/com/ibm/cldk/entities/RecordComponent.java
+++ b/src/main/java/com/ibm/cldk/entities/RecordComponent.java
@@ -5,13 +5,55 @@
import java.util.ArrayList;
import java.util.List;
+/**
+ * Represents a component of a record in the source code.
+ *
+ *
+ * This class encapsulates information about the component's name, type, modifiers,
+ * annotations, default value, and whether it is a varargs parameter.
+ *
+ *
+ *
+ * This class leverages Lombok's {@code @Data} annotation to automatically generate
+ * getters, setters, {@code toString()}, {@code equals()}, and {@code hashCode()} methods.
+ *
+ *
+ *
+ * Example usage:
+ *
+ * RecordComponent component = new RecordComponent();
+ * component.setName("exampleComponent");
+ * component.setType("String");
+ * component.setModifiers(Arrays.asList("private"));
+ * component.setAnnotations(Arrays.asList("NotNull"));
+ * component.setDefaultValue("defaultValue");
+ * component.setVarArgs(false);
+ *
+ *
+ *
+ * @author Rahul Krishna
+ * @version 2.3.0
+ */
@Data
public class RecordComponent {
- private String comment;
+ /** The comment associated with the record component. */
+ private Comment comment;
+
+ /** The name of the record component. */
private String name;
+
+ /** The type of the record component. */
private String type;
+
+ /** A list of modifiers applied to the record component (e.g., final, static). */
private List modifiers;
+
+ /** A list of annotations applied to the record component. */
private List annotations = new ArrayList<>();
- private Object defaultValue = null; // We will store the string representation of the default value
+
+ /** The default value of the record component, stored as a string representation. */
+ private Object defaultValue = null;
+
+ /** Indicates whether the record component is a varargs parameter. */
private boolean isVarArgs = false;
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/ibm/cldk/entities/Type.java b/src/main/java/com/ibm/cldk/entities/Type.java
index 8fc5ab6b..39b82236 100644
--- a/src/main/java/com/ibm/cldk/entities/Type.java
+++ b/src/main/java/com/ibm/cldk/entities/Type.java
@@ -7,27 +7,75 @@
import java.util.List;
import java.util.Map;
+/**
+ * Represents a type in the system with various characteristics.
+ * This class uses Lombok's @Data annotation to generate boilerplate code.
+ *
+ * @author Rahul Krishna
+ * @version 2.3.0
+ */
@Data
public class Type {
+ /** Indicates if this type is nested. */
private boolean isNestedType;
+
+ /** Indicates if this type is a class or interface declaration. */
private boolean isClassOrInterfaceDeclaration;
+
+ /** Indicates if this type is an enum declaration. */
private boolean isEnumDeclaration;
+
+ /** Indicates if this type is an annotation declaration. */
private boolean isAnnotationDeclaration;
+
+ /** Indicates if this type is a record declaration. */
private boolean isRecordDeclaration;
+
+ /** Indicates if this type is an interface. */
private boolean isInterface;
+
+ /** Indicates if this type is an inner class. */
private boolean isInnerClass;
+
+ /** Indicates if this type is a local class. */
private boolean isLocalClass;
+
+ /** List of types that this type extends. */
private List extendsList = new ArrayList<>();
- private String comment;
+
+ /** List of comments associated with this type. */
+ private List comments;
+
+ /** List of interfaces that this type implements. */
private List implementsList = new ArrayList<>();
+
+ /** List of modifiers for this type. */
private List modifiers = new ArrayList<>();
+
+ /** List of annotations for this type. */
private List annotations = new ArrayList<>();
+
+ /** The parent type of this type. */
private String parentType;
+
+ /** List of nested type declarations within this type. */
private List nestedTypeDeclarations = new ArrayList<>();
+
+ /** Map of callable declarations within this type. */
private Map callableDeclarations = new HashMap<>();
+
+ /** List of field declarations within this type. */
private List fieldDeclarations = new ArrayList<>();
+
+ /** List of enum constants within this type. */
private List enumConstants = new ArrayList<>();
+
+ /** List of record components within this type. */
private List recordComponents = new ArrayList<>();
+
+ /** List of initialization blocks within this type. */
private List initializationBlocks = new ArrayList<>();
+
+ /** Indicates if this type is an entry point class. */
private boolean isEntrypointClass = false;
}
\ No newline at end of file
diff --git a/src/main/java/com/ibm/cldk/entities/VariableDeclaration.java b/src/main/java/com/ibm/cldk/entities/VariableDeclaration.java
index 6263c6e4..c9284aea 100644
--- a/src/main/java/com/ibm/cldk/entities/VariableDeclaration.java
+++ b/src/main/java/com/ibm/cldk/entities/VariableDeclaration.java
@@ -2,13 +2,60 @@
import lombok.Data;
+/**
+ * Represents a variable declaration in the source code.
+ *
+ *
+ * This class encapsulates information about the variable's name, type, initializer,
+ * and its position within the source file. It also includes an optional comment
+ * associated with the variable declaration.
+ *
+ *
+ *
+ * This class leverages Lombok's {@code @Data} annotation to automatically generate
+ * getters, setters, {@code toString()}, {@code equals()}, and {@code hashCode()} methods.
+ *
+ *
+ *
+ * Example usage:
+ *
+ * VariableDeclaration varDecl = new VariableDeclaration();
+ * varDecl.setName("exampleVar");
+ * varDecl.setType("String");
+ * varDecl.setInitializer("\"defaultValue\"");
+ * varDecl.setStartLine(10);
+ * varDecl.setEndLine(10);
+ * varDecl.setStartColumn(5);
+ * varDecl.setEndColumn(20);
+ *
+ *
+ *
+ * @author Rahul Krishna
+ * @version 2.3.0
+ */
@Data
public class VariableDeclaration {
+ /** The comment associated with the variable declaration. */
+ private Comment comment;
+
+ /** The name of the variable. */
private String name;
+
+ /** The type of the variable. */
private String type;
+
+ /** The initializer of the variable, stored as a string representation. */
private String initializer;
- private int startLine;
- private int startColumn;
- private int endLine;
- private int endColumn;
+
+ /** The starting line number of the variable declaration in the source file. */
+ private int startLine = -1;
+
+ /** The starting column number of the variable declaration in the source file. */
+ private int startColumn = -1;
+
+ /** The ending line number of the variable declaration in the source file. */
+ private int endLine = -1;
+
+ /** The ending column number of the variable declaration in the source file. */
+ private int endColumn = -1;
}
diff --git a/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java b/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java
index 6400c4e6..957e46d9 100644
--- a/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java
+++ b/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java
@@ -15,6 +15,7 @@
import static com.ibm.cldk.SymbolTable.declaredMethodsAndConstructors;
import com.ibm.cldk.entities.Callable;
+import com.ibm.cldk.entities.Comment;
import com.ibm.cldk.entities.ParameterInCallable;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
@@ -61,7 +62,7 @@ public static Map createAndPutNewCallableInSymbolTable(IMethod m
newCallable.setFilePath("");
newCallable.setImplicit(true);
newCallable.setConstructor(methodName.contains(""));
- newCallable.setComment("");
+ newCallable.setComments(new ArrayList<>());
newCallable.setModifiers(Stream.of(method.isPublic() ? "public" : null, method.isProtected() ? "protected" : null, method.isPrivate() ? "private" : null, method.isAbstract() ? "abstract" : null, method.isStatic() ? "static" : null, method.isFinal() ? "final" : null, method.isSynchronized() ? "synchronized" : null, method.isNative() ? "native" : null, method.isSynthetic() ? "synthetic" : null, method.isBridge() ? "bridge" : null).filter(Objects::nonNull).collect(Collectors.toList()));
newCallable.setCode("");
newCallable.setSignature(methodSignature);
diff --git a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java
index 118201dc..2fcbc836 100644
--- a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java
+++ b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java
@@ -364,4 +364,32 @@ void mustBeAbleToResolveInitializationBlocks() throws IOException, InterruptedEx
}
}
}
+
+ @Test
+ void mustBeAbleToExtractCommentBlocks() throws IOException, InterruptedException {
+ var runCodeAnalyzerOnCallGraphTest = container.execInContainer(
+ "bash", "-c",
+ String.format(
+ "export JAVA_HOME=%s && java -jar /opt/jars/codeanalyzer-%s.jar --input=/test-applications/init-blocks-test --analysis-level=1",
+ javaHomePath, codeanalyzerVersion
+ )
+ );
+
+ // Read the output JSON
+ Gson gson = new Gson();
+ JsonObject jsonObject = gson.fromJson(runCodeAnalyzerOnCallGraphTest.getStdout(), JsonObject.class);
+ JsonObject symbolTable = jsonObject.getAsJsonObject("symbol_table");
+ for (Map.Entry element : symbolTable.entrySet()) {
+ String key = element.getKey();
+ if (!key.endsWith("App.java")) {
+ continue;
+ }
+ JsonObject type = element.getValue().getAsJsonObject();
+ JsonArray comments = type.getAsJsonArray("comments");
+ Assertions.assertEquals(16, comments.size(), "Should have 15 comments");
+ Assertions.assertTrue(StreamSupport.stream(comments.spliterator(), false)
+ .map(JsonElement::getAsJsonObject)
+ .anyMatch(comment -> comment.get("is_javadoc").getAsBoolean()), "Single line comment not found");
+ }
+ }
}
diff --git a/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java b/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
index 7fbb3f07..bca83ebb 100644
--- a/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
+++ b/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java
@@ -1,21 +1,36 @@
+/**
+ * Static and Instance Initialization Blocks Example with comments.
+ *
+ * MIT License
+ *
+ */
package org.example;
+// Import statements
import java.util.List;
+/**
+ * The App class demonstrates the use of static and instance initialization blocks,
+ * as well as a constructor in Java.
+ */
public class App {
+ // Static field
private static String staticMessage;
+ // Static initialization block
static {
try {
staticMessage = "Static block initialized";
System.out.println("Static initialization block executed.");
- initializeStaticFields();
+ initializeStaticFields(); // Call a method to initialize static fields
} catch (Exception e) {
+ // Handle any exceptions that occur during initialization
System.err.println("Error in static block: " + e.getMessage());
- throw new RuntimeException(e);
+ throw new RuntimeException(e); // Rethrow the exception
}
}
+ // Instance initialization block
{
try {
System.out.println("Instance initialization block executed.");
@@ -25,19 +40,41 @@ public class App {
}
}
+ /**
+ * Constructor for the App class.
+ * Prints a message indicating that the constructor has been executed.
+ */
public App() {
System.out.println("Constructor executed.");
}
+ /**
+ * Initializes static fields.
+ * Prints a message indicating that static fields are being initialized.
+ */
private static void initializeStaticFields() {
System.out.println("Initializing static fields.");
}
+ /**
+ * Initializes instance fields.
+ * Prints a message indicating that instance fields are being initialized.
+ */
private void initializeInstanceFields() {
+ // This is a comment associated with the println statement below
System.out.println("Initializing instance fields.");
}
+ /**
+ * The main method is the entry point of the application.
+ * Creates a new instance of the App class.
+ *
+ * @param args Command line arguments
+ */
public static void main(String[] args) {
- new App();
+
+ // This is an orphaned comment
+
+ new App(); // Create a new instance of the App class
}
-}
+}
\ No newline at end of file
From a380f4370caac23af6babdc6292489c8bc8ade15 Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Wed, 19 Feb 2025 12:14:22 -0500
Subject: [PATCH 6/8] Issue 102: Add comment nodes eveywhere. Add a new field
in CompilationUnit to capture package information.
Signed-off-by: Rahul Krishna
---
.settings/org.eclipse.jdt.core.prefs | 5 ++
.../java/com/ibm/cldk/entities/Comment.java | 80 +++++++++++++++++++
2 files changed, 85 insertions(+)
create mode 100644 .settings/org.eclipse.jdt.core.prefs
create mode 100644 src/main/java/com/ibm/cldk/entities/Comment.java
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000..aa020513
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=ignore
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.source=11
diff --git a/src/main/java/com/ibm/cldk/entities/Comment.java b/src/main/java/com/ibm/cldk/entities/Comment.java
new file mode 100644
index 00000000..e248a1a0
--- /dev/null
+++ b/src/main/java/com/ibm/cldk/entities/Comment.java
@@ -0,0 +1,80 @@
+package com.ibm.cldk.entities;
+
+import lombok.Data;
+
+/**
+ * Represents a comment entity extracted from source code.
+ * This class encapsulates information about the content, position,
+ * and type of a comment within a source file.
+ *
+ *
+ * The comment can be of various types, including Javadoc, block comments, or line comments.
+ * The class also keeps track of the comment's position within the file (line and column numbers).
+ *
+ *
+ *
+ * This class leverages Lombok's {@code @Data} annotation to automatically generate
+ * getters, setters, {@code toString()}, {@code equals()}, and {@code hashCode()} methods.
+ *
+ *
+ * Example usage:
+ *
+ * Comment comment = new Comment();
+ * comment.setContent("This is a sample comment.");
+ * comment.setStartLine(10);
+ * comment.setEndLine(12);
+ * comment.setJavadoc(true);
+ *
+ *
+ * @author Rahul Krishna
+ * @version 2.3.0
+ */
+@Data
+public class Comment {
+
+ /**
+ * The textual content of the comment.
+ */
+ private String content;
+
+ /**
+ * The starting line number of the comment in the source file.
+ *
+ * Defaults to {@code -1} if the position is unknown.
+ *
+ */
+ private int startLine = -1;
+
+ /**
+ * The ending line number of the comment in the source file.
+ *
+ * Defaults to {@code -1} if the position is unknown.
+ *
+ */
+ private int endLine = -1;
+
+ /**
+ * The starting column number of the comment in the source file.
+ *
+ * Defaults to {@code -1} if the position is unknown.
+ *
+ */
+ private int startColumn = -1;
+
+ /**
+ * The ending column number of the comment in the source file.
+ *
+ * Defaults to {@code -1} if the position is unknown.
+ *
+ */
+ private int endColumn = -1;
+
+ /**
+ * Indicates whether the comment is a Javadoc comment.
+ *
+ * Javadoc comments are special block comments used for generating documentation
+ * and typically start with {@code /**}.
+ *
+ */
+ private boolean isJavadoc = false;
+}
From b59e68f6b5275c150cd444fa7bc762009891208b Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Wed, 19 Feb 2025 12:14:43 -0500
Subject: [PATCH 7/8] Issue 102: Add comment nodes eveywhere. Add a new field
in CompilationUnit to capture package information.
Signed-off-by: Rahul Krishna
---
.settings/org.eclipse.buildship.core.prefs | 13 -------------
.settings/org.eclipse.jdt.core.prefs | 5 -----
2 files changed, 18 deletions(-)
delete mode 100644 .settings/org.eclipse.buildship.core.prefs
delete mode 100644 .settings/org.eclipse.jdt.core.prefs
diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs
deleted file mode 100644
index a40ec5db..00000000
--- a/.settings/org.eclipse.buildship.core.prefs
+++ /dev/null
@@ -1,13 +0,0 @@
-arguments=
-auto.sync=false
-build.scans.enabled=false
-connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
-connection.project.dir=
-eclipse.preferences.version=1
-gradle.user.home=
-java.home=
-jvm.arguments=
-offline.mode=false
-override.workspace.settings=false
-show.console.view=true
-show.executions.view=true
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index aa020513..00000000
--- a/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,5 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=ignore
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
-org.eclipse.jdt.core.compiler.compliance=11
-org.eclipse.jdt.core.compiler.source=11
From d7e5c5aa39298c899d0bc20179b566020c7c7f2f Mon Sep 17 00:00:00 2001
From: Rahul Krishna
Date: Wed, 19 Feb 2025 12:27:49 -0500
Subject: [PATCH 8/8] Issue 102: Add file level javadoc comment.
Signed-off-by: Rahul Krishna
---
src/main/java/com/ibm/cldk/SymbolTable.java | 15 ++++++++++++---
.../ibm/cldk/entities/JavaCompilationUnit.java | 4 +++-
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java
index f99b38c6..8783594b 100644
--- a/src/main/java/com/ibm/cldk/SymbolTable.java
+++ b/src/main/java/com/ibm/cldk/SymbolTable.java
@@ -136,11 +136,21 @@ private static JavaCompilationUnit processCompilationUnit(CompilationUnit parseR
cUnit.setFilePath(parseResult.getStorage().map(s -> s.getPath().toString()).orElse(""));
- com.ibm.cldk.entities.Comment comment = new com.ibm.cldk.entities.Comment();
+ // Set file level comment
+ parseResult.getAllComments().stream().findFirst().ifPresent(c -> {
+ com.ibm.cldk.entities.Comment fileComment = new com.ibm.cldk.entities.Comment();
+ fileComment.setContent(c.getContent());
+ fileComment.setStartLine(c.getRange().isPresent() ? c.getRange().get().begin.line : -1);
+ fileComment.setEndLine(c.getRange().isPresent() ? c.getRange().get().end.line : -1);
+ fileComment.setStartColumn(c.getRange().isPresent() ? c.getRange().get().begin.column : -1);
+ fileComment.setEndColumn(c.getRange().isPresent() ? c.getRange().get().end.column : -1);
+ fileComment.setJavadoc(c.isJavadocComment());
+ cUnit.getComments().add(fileComment);
+ });
// Add class comment
cUnit.setComments(
- parseResult.getAllContainedComments().stream().map(c -> {
+ parseResult.getAllComments().stream().map(c -> {
com.ibm.cldk.entities.Comment fileComment = new com.ibm.cldk.entities.Comment();
fileComment.setContent(c.getContent());
fileComment.setStartLine(c.getRange().isPresent() ? c.getRange().get().begin.line : -1);
@@ -154,7 +164,6 @@ private static JavaCompilationUnit processCompilationUnit(CompilationUnit parseR
// Set package name
cUnit.setPackageName(parseResult.getPackageDeclaration().map(NodeWithName::getNameAsString).orElse(""));
-
// Add javadoc comment
// Add imports
cUnit.setImports(
diff --git a/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java b/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java
index 2f560c9c..526ee978 100644
--- a/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java
+++ b/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java
@@ -1,6 +1,8 @@
package com.ibm.cldk.entities;
import lombok.Data;
+
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -8,7 +10,7 @@
public class JavaCompilationUnit {
private String filePath;
private String packageName;
- private List comments;
+ private List comments = new ArrayList<>();
private List imports;
private Map typeDeclarations;
private boolean isModified;