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/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/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index 2dbef6d2..8783594b 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,20 +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 org.checkerframework.checker.units.qual.C; - -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 { @@ -58,175 +89,359 @@ 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() : ""); - + // 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.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); + 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 */ - // 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) { + InitializationBlock initializationBlock = new InitializationBlock(); + initializationBlock.setFilePath(filePath); + + 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.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; + } + 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 + * 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 -> { 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) { @@ -236,19 +451,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); } @@ -256,12 +478,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; } } @@ -283,7 +512,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; } } @@ -299,7 +532,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; } @@ -334,7 +568,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()); @@ -347,7 +582,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)) { @@ -357,7 +593,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; } @@ -366,7 +606,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")); @@ -385,7 +626,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; } @@ -399,8 +641,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); @@ -417,7 +661,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 @@ -427,37 +672,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); @@ -471,41 +739,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") || @@ -524,8 +795,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") @@ -542,18 +812,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. * @@ -562,13 +837,30 @@ 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 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 * declared field and returns a JSON object containing the extracted @@ -581,16 +873,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())); @@ -612,16 +918,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); @@ -642,9 +953,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); @@ -669,20 +993,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)) { @@ -705,7 +1033,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()) { @@ -724,14 +1052,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); @@ -751,35 +1083,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)) { @@ -787,7 +1132,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 = ""; @@ -798,25 +1144,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) @@ -828,17 +1178,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) @@ -870,9 +1216,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); @@ -909,7 +1268,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); @@ -953,20 +1313,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, Map>> extractAll(Path projectRootPath) throws IOException { + public static Pair, Map>> extractAll(Path projectRootPath) + throws IOException { SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy(); ProjectRoot projectRoot = symbolSolverCollectionStrategy.collect(projectRootPath); - javaSymbolSolver = (JavaSymbolSolver) symbolSolverCollectionStrategy.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21).getSymbolResolver().get(); + javaSymbolSolver = (JavaSymbolSolver) symbolSolverCollectionStrategy.getParserConfiguration() + .setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21).getSymbolResolver().get(); Map symbolTable = new LinkedHashMap<>(); Map> parseProblems = new HashMap<>(); for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { for (ParseResult parseResult : sourceRoot.tryToParse()) { if (parseResult.isSuccessful()) { CompilationUnit compilationUnit = parseResult.getResult().get(); - symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), processCompilationUnit(compilationUnit)); + symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), + processCompilationUnit(compilationUnit)); } else { parseProblems.put(sourceRoot.getRoot().toString(), parseResult.getProblems()); } @@ -975,14 +1338,16 @@ public static Pair, Map>> return Pair.of(symbolTable, parseProblems); } - public static Pair, Map>> extractSingle(String code) throws IOException { + public static Pair, Map>> extractSingle(String code) + throws IOException { Map symbolTable = new LinkedHashMap(); Map parseProblems = new HashMap>(); // Setting up symbol solvers CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver(); combinedTypeSolver.add(new ReflectionTypeSolver()); - ParserConfiguration parserConfiguration = new ParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); + ParserConfiguration parserConfiguration = new ParserConfiguration() + .setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); parserConfiguration.setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver)); JavaParser javaParser = new JavaParser(parserConfiguration); @@ -1007,14 +1372,17 @@ public static Pair, Map>> * @return * @throws IOException */ - public static Pair, Map>> extract(Path projectRootPath, List javaFilePaths) throws IOException { + public static Pair, Map>> extract(Path projectRootPath, + List javaFilePaths) throws IOException { // create symbol solver and parser configuration SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy(); ProjectRoot projectRoot = symbolSolverCollectionStrategy.collect(projectRootPath); - javaSymbolSolver = (JavaSymbolSolver) symbolSolverCollectionStrategy.getParserConfiguration().getSymbolResolver().get(); + javaSymbolSolver = (JavaSymbolSolver) symbolSolverCollectionStrategy.getParserConfiguration() + .getSymbolResolver().get(); Log.info("Setting parser language level to JAVA_21"); - ParserConfiguration parserConfiguration = new ParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); + ParserConfiguration parserConfiguration = new ParserConfiguration() + .setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); parserConfiguration.setSymbolResolver(javaSymbolSolver); // create java parser with the configuration @@ -1029,7 +1397,8 @@ public static Pair, Map>> if (parseResult.isSuccessful()) { CompilationUnit compilationUnit = parseResult.getResult().get(); System.out.println("Successfully parsed file: " + javaFilePath.toString()); - symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), processCompilationUnit(compilationUnit)); + symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), + processCompilationUnit(compilationUnit)); } else { Log.error(parseResult.getProblems().toString()); parseProblems.put(javaFilePath.toString(), parseResult.getProblems()); diff --git a/src/main/java/com/ibm/cldk/entities/CallSite.java b/src/main/java/com/ibm/cldk/entities/CallSite.java index 6381dbbc..c3d92e53 100644 --- a/src/main/java/com/ibm/cldk/entities/CallSite.java +++ b/src/main/java/com/ibm/cldk/entities/CallSite.java @@ -6,26 +6,81 @@ import java.util.List; import java.util.Optional; +/** + * Represents a call site within source code, encapsulating information about method invocations + * and their contextual details. + * + *

+ * A call site contains information about the method being called, its receiver, + * arguments, return type, and various properties that characterize the method call. + * It also tracks the position of the call site 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. + *

+ * + * @author Rahul Krishna + * @version 2.3.0 + */ @Data @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class CallSite { + /** Name of the method being called */ private String methodName; + + /** Comment associated with the call site */ + private Comment comment; + + /** Expression representing the receiver of the method call */ private String receiverExpr; + + /** Type of the receiver object */ private String receiverType; + + /** List of argument types for the method call */ private List argumentTypes; + + /** Return type of the called method */ private String returnType; + + /** Full signature of the callee method */ private String calleeSignature; - // Access specifiers + + /** Flag indicating if the method has public access */ private boolean isPublic = false; + + /** Flag indicating if the method has protected access */ private boolean isProtected = false; + + /** Flag indicating if the method has private access */ private boolean isPrivate = false; + + /** Flag indicating if the method has unspecified access */ private boolean isUnspecified = false; + + /** Flag indicating if this is a static method call */ private boolean isStaticCall; + + /** Flag indicating if this is a constructor call */ private boolean isConstructorCall; + + /** CRUD operation associated with this call site, if any */ private CRUDOperation crudOperation = null; + + /** CRUD query associated with this call site, if any */ private CRUDQuery crudQuery = null; + + /** Starting line number of the call site in the source file */ private int startLine; + + /** Starting column number of the call site in the source file */ private int startColumn; + + /** Ending line number of the call site in the source file */ private int endLine; + + /** Ending column number of the call site in the source file */ private int endColumn; -} +} \ No newline at end of file diff --git a/src/main/java/com/ibm/cldk/entities/Callable.java b/src/main/java/com/ibm/cldk/entities/Callable.java index 20ffe3af..04d91ee2 100644 --- a/src/main/java/com/ibm/cldk/entities/Callable.java +++ b/src/main/java/com/ibm/cldk/entities/Callable.java @@ -5,28 +5,101 @@ import java.util.ArrayList; import java.util.List; +/** + * Represents a callable entity in the source code, such as a method or constructor. + * + *

+ * This class encapsulates information about the callable's file path, signature, comments, + * annotations, modifiers, thrown exceptions, declaration, parameters, code, position within + * the source file, return type, and various properties that characterize the callable. + *

+ * + *

+ * This class leverages Lombok's {@code @Data} annotation to automatically generate + * getters, setters, {@code toString()}, {@code equals()}, and {@code hashCode()} methods. + *

+ * + *

+ * Example usage: + *

+ *     Callable callable = new Callable();
+ *     callable.setFilePath("src/main/java/com/ibm/cldk/entities/Example.java");
+ *     callable.setSignature("public void exampleMethod()");
+ *     callable.setStartLine(10);
+ *     callable.setEndLine(20);
+ *     callable.setReturnType("void");
+ *     callable.setConstructor(false);
+ * 
+ *

+ * + * @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/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; +} 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 new file mode 100644 index 00000000..d10e1f56 --- /dev/null +++ b/src/main/java/com/ibm/cldk/entities/InitializationBlock.java @@ -0,0 +1,24 @@ +package com.ibm.cldk.entities; + +import lombok.Data; + +import java.util.List; +import java.util.stream.Collector; + +@Data +public class InitializationBlock { + private String filePath; + private List comments; + 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/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java b/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java index 2ed0095e..526ee978 100644 --- a/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java +++ b/src/main/java/com/ibm/cldk/entities/JavaCompilationUnit.java @@ -1,13 +1,16 @@ package com.ibm.cldk.entities; import lombok.Data; + +import java.util.ArrayList; import java.util.List; import java.util.Map; @Data public class JavaCompilationUnit { private String filePath; - private String comment; + private String packageName; + private List comments = new ArrayList<>(); 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 d6ce7290..39b82236 100644 --- a/src/main/java/com/ibm/cldk/entities/Type.java +++ b/src/main/java/com/ibm/cldk/entities/Type.java @@ -7,26 +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 24f1975a..2fcbc836 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,63 @@ 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"); + } + } + } + + @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/.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..bca83ebb --- /dev/null +++ b/src/test/resources/test-applications/init-blocks-test/app/src/main/java/org/example/App.java @@ -0,0 +1,80 @@ +/** + * 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(); // 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); // Rethrow the exception + } + } + + // Instance initialization block + { + try { + System.out.println("Instance initialization block executed."); + initializeInstanceFields(); + } catch (Exception e) { + System.err.println("Error in instance block: " + e.getMessage()); + } + } + + /** + * 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) { + + // This is an orphaned comment + + new App(); // Create a new instance of the App class + } +} \ No newline at end of file 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")