modifiers) {
- return modifiers.stream()
- .filter(mod ->
- ModifierKind.PUBLIC != mod
- && ModifierKind.PROTECTED != mod
- && ModifierKind.PRIVATE != mod)
- .map(this::convertSpoonModifier)
- .toList();
- }
-
- private SourceLocation convertSpoonPosition(SourcePosition position) {
- return position.isValidPosition()
- ? new SourceLocation(
- position.getFile() != null ? position.getFile().toPath() : null,
- position.getLine())
- : SourceLocation.NO_LOCATION;
- }
-
- private boolean isExported(CtType> type) {
- return
- (type.isPublic() || (type.isProtected() && !isEffectivelyFinal(type)))
- && isParentExported(type);
- }
-
- private boolean isExported(CtTypeMember member) {
- return (member.isPublic() || (member.isProtected() && !isEffectivelyFinal(member.getDeclaringType())))
- && isParentExported(member);
- }
-
- private boolean isParentExported(CtTypeMember member) {
- return member.getDeclaringType() == null || isExported(member.getDeclaringType());
- }
-
- private boolean isEffectivelyFinal(CtType> type) {
- if (type instanceof CtClass> cls)
- if (!cls.getConstructors().isEmpty()
- && cls.getConstructors().stream().noneMatch(cons -> cons.isPublic() || cons.isProtected()))
- return true;
-
- return type.isFinal() || type.hasModifier(ModifierKind.SEALED);
- }
-
- private String makeQualifiedName(CtTypeMember member) {
- return String.format("%s.%s", member.getDeclaringType().getQualifiedName(), member.getSimpleName());
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Symbol.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Symbol.java
deleted file mode 100644
index 48e54e63..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Symbol.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.github.maracas.roseau.api.model;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * This abstract class represents a symbol in the library, which can be a type,
- * a method, a constructor, or a field.
- *
- * It provides information about the symbol's qualified qualifiedName, visibility, modifiers,
- * and position within the source code.
- */
-public abstract sealed class Symbol permits TypeDecl, TypeMemberDecl {
- /**
- * The qualifiedName of the symbol.
- */
- protected final String qualifiedName;
-
- /**
- * The visibility of the symbol.
- */
- protected final AccessModifier visibility;
-
- /**
- * List of non-access modifiers applied to the symbol.
- */
- protected final List modifiers;
-
- /**
- * The exact location of the symbol
- */
- protected final SourceLocation location;
-
- protected Symbol(String qualifiedName, AccessModifier visibility, List modifiers, SourceLocation location) {
- this.qualifiedName = qualifiedName;
- this.visibility = visibility;
- this.modifiers = modifiers;
- this.location = location;
- }
-
- /**
- * Retrieves the qualifiedName of the symbol.
- *
- * @return The symbol's qualifiedName
- */
- public String getQualifiedName() {
- return qualifiedName;
- }
-
- /**
- * Retrieves the visibility of the symbol.
- *
- * @return The symbol's visibility
- */
- public AccessModifier getVisibility() {
- return visibility;
- }
-
- /**
- * Checks whether the symbol is accessible/exported
- *
- * @return exported or not
- */
- @JsonIgnore
- public abstract boolean isExported();
-
- /**
- * Retrieves the list of non-access modifiers applied to the symbol.
- *
- * @return The symbol's non-access modifiers
- */
- public List getModifiers() {
- return modifiers;
- }
-
- /**
- * Retrieves the position of the symbol.
- *
- * @return The symbol's position.
- */
- public SourceLocation getLocation() {
- return location;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Symbol symbol = (Symbol) o;
- return Objects.equals(qualifiedName, symbol.qualifiedName)
- && visibility == symbol.visibility
- && Objects.equals(modifiers, symbol.modifiers)
- && Objects.equals(location, symbol.location);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(qualifiedName, visibility, modifiers, location);
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeDecl.java
deleted file mode 100644
index 7a830be2..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeDecl.java
+++ /dev/null
@@ -1,271 +0,0 @@
-package com.github.maracas.roseau.api.model;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.github.maracas.roseau.api.model.reference.ITypeReference;
-import com.github.maracas.roseau.api.model.reference.TypeReference;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-
-/**
- * Represents a type declaration in the library.
- * This class extends the {@link Symbol} class and contains information about the type's kind, fields, methods, constructors, and more.
- */
-@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "typeKind")
-public abstract sealed class TypeDecl extends Symbol permits ClassDecl, InterfaceDecl, AnnotationDecl {
- protected final List> implementedInterfaces;
-
- /**
- * List of formal type parameters for generic types.
- */
- protected final List formalTypeParameters;
-
- /**
- * List of fields declared within the type.
- */
- protected final List fields;
-
- /**
- * List of methods declared within the type.
- */
- protected final List methods;
-
- protected final TypeReference enclosingType;
-
- protected TypeDecl(String qualifiedName,
- AccessModifier visibility,
- List modifiers,
- SourceLocation location,
- List> implementedInterfaces,
- List formalTypeParameters,
- List fields,
- List methods,
- TypeReference enclosingType) {
- super(qualifiedName, visibility, modifiers, location);
- this.implementedInterfaces = implementedInterfaces;
- this.formalTypeParameters = formalTypeParameters;
- this.fields = fields;
- this.methods = methods;
- this.enclosingType = enclosingType;
- }
-
- @JsonIgnore
- @Override
- public boolean isExported() {
- return (isPublic() || (isProtected() && !isEffectivelyFinal()))
- && (enclosingType == null || enclosingType.getResolvedApiType().map(TypeDecl::isExported).orElse(true));
- }
-
- @JsonIgnore
- public boolean isNested() {
- return enclosingType != null;
- }
-
- @JsonIgnore
- public boolean isClass() {
- return false;
- }
-
- @JsonIgnore
- public boolean isInterface() {
- return false;
- }
-
- @JsonIgnore
- public boolean isEnum() {
- return false;
- }
-
- @JsonIgnore
- public boolean isRecord() {
- return false;
- }
-
- @JsonIgnore
- public boolean isAnnotation() {
- return false;
- }
-
- @JsonIgnore
- public boolean isCheckedException() {
- return false;
- }
-
- @JsonIgnore
- public boolean isStatic() {
- return modifiers.contains(Modifier.STATIC);
- }
-
- @JsonIgnore
- public boolean isFinal() {
- return modifiers.contains(Modifier.FINAL);
- }
-
- @JsonIgnore
- public boolean isSealed() {
- return modifiers.contains(Modifier.SEALED);
- }
-
- @JsonIgnore
- public boolean isEffectivelyFinal() {
- // FIXME: in fact, a sealed class may not be final if one of its permitted subclass
- // is explicitly marked as non-sealed
- return !modifiers.contains(Modifier.NON_SEALED) && (isFinal() || isSealed());
- }
-
- @JsonIgnore
- public boolean isPublic() {
- return AccessModifier.PUBLIC == visibility;
- }
-
- @JsonIgnore
- public boolean isProtected() {
- return AccessModifier.PROTECTED == visibility;
- }
-
- @JsonIgnore
- public boolean isPrivate() {
- return AccessModifier.PRIVATE == visibility;
- }
-
- @JsonIgnore
- public boolean isPackagePrivate() {
- return AccessModifier.PACKAGE_PRIVATE == visibility;
- }
-
- @JsonIgnore
- public boolean isAbstract() {
- return modifiers.contains(Modifier.ABSTRACT);
- }
-
- @JsonIgnore
- public List getAllMethods() {
- List allMethods = Stream.concat(
- methods.stream(),
- getSuperMethods().stream()
- ).toList();
-
- return allMethods.stream()
- .filter(m -> allMethods.stream().noneMatch(m2 -> !m2.equals(m) && m2.isOverriding(m)))
- .toList();
- }
-
- @JsonIgnore
- public List> getAllSuperTypes() {
- return new ArrayList<>(getAllImplementedInterfaces());
- }
-
- protected List getSuperMethods() {
- return implementedInterfaces.stream()
- .map(TypeReference::getResolvedApiType)
- .flatMap(Optional::stream)
- .map(InterfaceDecl::getAllMethods)
- .flatMap(Collection::stream)
- .toList();
- }
-
- @JsonIgnore
- public List getAllFields() {
- return Stream.concat(
- fields.stream(),
- implementedInterfaces.stream()
- .map(TypeReference::getResolvedApiType)
- .flatMap(Optional::stream)
- .map(InterfaceDecl::getAllFields)
- .flatMap(Collection::stream)
- ).toList();
- }
-
- /**
- * Retrieves the superinterfaces of the type as typeDeclarations.
- *
- * @return Type's superinterfaces as typeDeclarations
- */
- public List> getImplementedInterfaces() {
- return implementedInterfaces;
- }
-
- /**
- * Retrieves the list of formal type parameters for generic types.
- *
- * @return List of formal type parameters
- */
- public List getFormalTypeParameters() {
- return formalTypeParameters;
- }
-
- /**
- * Retrieves the list of fields declared within the type.
- *
- * @return List of fields declared within the type
- */
- public List getFields() {
- return fields;
- }
-
- public List getMethods() {
- return methods;
- }
-
- public Optional> getEnclosingType() {
- return Optional.ofNullable(enclosingType);
- }
-
- public Optional findField(String name) {
- return fields.stream()
- .filter(f -> f.getSimpleName().equals(name))
- .findFirst();
- }
-
- public Optional findMethod(String name, List extends ITypeReference> parameterTypes, boolean varargs) {
- return methods.stream()
- .filter(m -> m.hasSignature(name, parameterTypes, varargs))
- .findFirst();
- }
-
- public Optional findMethod(String name, List extends ITypeReference> parameterTypes) {
- return findMethod(name, parameterTypes, false);
- }
-
- public Optional findMethod(String name) {
- return methods.stream()
- .filter(m -> m.getSimpleName().equals(name))
- .findFirst();
- }
-
- @JsonIgnore
- public List> getAllImplementedInterfaces() {
- return Stream.concat(
- implementedInterfaces.stream(),
- implementedInterfaces.stream()
- .map(TypeReference::getResolvedApiType)
- .flatMap(Optional::stream)
- .map(InterfaceDecl::getAllImplementedInterfaces)
- .flatMap(Collection::stream)
- ).distinct().toList();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
- TypeDecl typeDecl = (TypeDecl) o;
- return Objects.equals(implementedInterfaces, typeDecl.implementedInterfaces)
- && Objects.equals(formalTypeParameters, typeDecl.formalTypeParameters)
- && Objects.equals(fields, typeDecl.fields)
- && Objects.equals(methods, typeDecl.methods);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), implementedInterfaces, formalTypeParameters, fields, methods);
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMember.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMember.java
deleted file mode 100644
index 8f00d0cc..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMember.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.github.maracas.roseau.api.model;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.github.maracas.roseau.api.model.reference.ITypeReference;
-import com.github.maracas.roseau.api.model.reference.TypeReference;
-
-public interface TypeMember {
- TypeReference getContainingType();
- ITypeReference getType();
- @JsonIgnore
- String getSimpleName();
- @JsonIgnore
- boolean isStatic();
- @JsonIgnore
- boolean isFinal();
- @JsonIgnore
- boolean isPublic();
- @JsonIgnore
- boolean isProtected();
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMemberDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMemberDecl.java
deleted file mode 100644
index 0d219541..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMemberDecl.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.github.maracas.roseau.api.model;
-
-import com.github.maracas.roseau.api.model.reference.ITypeReference;
-import com.github.maracas.roseau.api.model.reference.TypeReference;
-
-import java.util.List;
-import java.util.Objects;
-
-public abstract sealed class TypeMemberDecl extends Symbol implements TypeMember permits FieldDecl, ExecutableDecl {
- protected final TypeReference containingType;
- protected final ITypeReference type;
-
- protected TypeMemberDecl(String qualifiedName, AccessModifier visibility, List modifiers,
- SourceLocation location, TypeReference containingType, ITypeReference type) {
- super(qualifiedName, visibility, modifiers, location);
- this.containingType = containingType;
- this.type = type;
- }
-
- @Override
- public TypeReference getContainingType() {
- return containingType;
- }
-
- @Override
- public ITypeReference getType() {
- return type;
- }
-
- @Override
- public boolean isExported() {
- return (isPublic()
- || (isProtected() && !containingType.getResolvedApiType().map(TypeDecl::isEffectivelyFinal).orElse(true)))
- && containingType.getResolvedApiType().map(TypeDecl::isExported).orElse(true);
- }
-
- @Override
- public boolean isStatic() {
- return modifiers.contains(Modifier.STATIC);
- }
-
- @Override
- public boolean isFinal() {
- return modifiers.contains(Modifier.FINAL);
- }
-
- @Override
- public boolean isPublic() {
- return AccessModifier.PUBLIC == visibility;
- }
-
- @Override
- public boolean isProtected() {
- return AccessModifier.PROTECTED == visibility;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
- TypeMemberDecl other = (TypeMemberDecl) o;
- return Objects.equals(type, other.type);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), type);
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/package-info.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/package-info.java
deleted file mode 100644
index 7ba99615..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * This package contains the classes and types needed for API extraction.
- */
-package com.github.maracas.roseau.api.model;
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ArrayTypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ArrayTypeReference.java
deleted file mode 100644
index 32c777d1..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ArrayTypeReference.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.github.maracas.roseau.api.model.reference;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-
-public record ArrayTypeReference(ITypeReference componentType) implements ITypeReference {
- @JsonIgnore
- @Override
- public String getQualifiedName() {
- return componentType().getQualifiedName() + "[]";
- }
-
- @Override
- public String toString() {
- return getQualifiedName();
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ITypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ITypeReference.java
deleted file mode 100644
index b0b70daf..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ITypeReference.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.github.maracas.roseau.api.model.reference;
-
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-
-@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "refKind")
-public sealed interface ITypeReference
- permits TypeReference, ArrayTypeReference, PrimitiveTypeReference, TypeParameterReference {
- String getQualifiedName();
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/PrimitiveTypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/PrimitiveTypeReference.java
deleted file mode 100644
index 4a3914e1..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/PrimitiveTypeReference.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.github.maracas.roseau.api.model.reference;
-
-public record PrimitiveTypeReference(String qualifiedName) implements ITypeReference {
- @Override
- public String getQualifiedName() {
- return qualifiedName;
- }
-
- @Override
- public String toString() {
- return qualifiedName;
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/SpoonTypeReferenceFactory.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/SpoonTypeReferenceFactory.java
deleted file mode 100644
index 5cf9be0b..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/SpoonTypeReferenceFactory.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.github.maracas.roseau.api.model.reference;
-
-import com.github.maracas.roseau.api.model.SpoonAPIFactory;
-import com.github.maracas.roseau.api.model.TypeDecl;
-
-import java.util.List;
-
-public class SpoonTypeReferenceFactory implements TypeReferenceFactory {
- private final SpoonAPIFactory apiFactory;
-
- public SpoonTypeReferenceFactory(SpoonAPIFactory apiFactory) {
- this.apiFactory = apiFactory;
- }
-
- @Override
- public TypeReference createTypeReference(String qualifiedName) {
- return new TypeReference<>(qualifiedName, apiFactory);
- }
-
- @Override
- public PrimitiveTypeReference createPrimitiveTypeReference(String name) {
- return new PrimitiveTypeReference(name);
- }
-
- @Override
- public ArrayTypeReference createArrayTypeReference(ITypeReference componentType) {
- return componentType != null ? new ArrayTypeReference(componentType) : null;
- }
-
- @Override
- public TypeParameterReference createTypeParameterReference(String qualifiedName, List bounds) {
- return new TypeParameterReference(qualifiedName, bounds);
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeParameterReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeParameterReference.java
deleted file mode 100644
index 216d0034..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeParameterReference.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.github.maracas.roseau.api.model.reference;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-public record TypeParameterReference(String qualifiedName, List bounds) implements ITypeReference {
- @Override
- public String getQualifiedName() {
- return qualifiedName;
- }
-
- @Override
- public String toString() {
- return "%s<%s>".formatted(qualifiedName,
- bounds.stream().map(Object::toString).collect(Collectors.joining(", ")));
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReference.java
deleted file mode 100644
index cf39a949..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReference.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.github.maracas.roseau.api.model.reference;
-
-import com.fasterxml.jackson.annotation.JsonValue;
-import com.github.maracas.roseau.api.model.SpoonAPIFactory;
-import com.github.maracas.roseau.api.model.TypeDecl;
-import com.google.common.base.Objects;
-
-import java.util.Optional;
-
-public final class TypeReference implements ITypeReference {
- private final String qualifiedName;
- private SpoonAPIFactory factory;
- private T resolvedApiType;
-
- public TypeReference(String qualifiedName) {
- this.qualifiedName = qualifiedName;
- }
-
- public TypeReference(String qualifiedName, SpoonAPIFactory factory) {
- this.qualifiedName = qualifiedName;
- this.factory = factory;
- }
-
- @JsonValue
- @Override
- public String getQualifiedName() {
- return qualifiedName;
- }
-
- public void setFactory(SpoonAPIFactory factory) {
- this.factory = factory;
- }
-
- public Optional getResolvedApiType() {
- if (resolvedApiType == null && factory != null)
- resolvedApiType = (T) factory.convertCtType(qualifiedName);
-
- return Optional.ofNullable(resolvedApiType);
- }
-
- public void setResolvedApiType(T type) {
- resolvedApiType = type;
- }
-
- public boolean isSubtypeOf(TypeReference other) {
- return equals(other) || getResolvedApiType().map(t -> t.getAllSuperTypes().contains(other)).orElse(false);
- }
-
- public boolean isSameHierarchy(TypeReference other) {
- return isSubtypeOf(other) || other.isSubtypeOf(this);
- }
-
- @Override
- public String toString() {
- return qualifiedName;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- TypeReference> other = (TypeReference>) o;
- return Objects.equal(qualifiedName, other.qualifiedName);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(qualifiedName);
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReferenceFactory.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReferenceFactory.java
deleted file mode 100644
index 4b5632ef..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReferenceFactory.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.github.maracas.roseau.api.model.reference;
-
-import com.github.maracas.roseau.api.model.TypeDecl;
-
-import java.util.List;
-
-public interface TypeReferenceFactory {
- TypeReference createTypeReference(String qualifiedName);
- PrimitiveTypeReference createPrimitiveTypeReference(String name);
- ArrayTypeReference createArrayTypeReference(ITypeReference componentType);
- TypeParameterReference createTypeParameterReference(String qualifiedName, List bounds);
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/APIDiff.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/APIDiff.java
deleted file mode 100644
index 80cf6015..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/APIDiff.java
+++ /dev/null
@@ -1,348 +0,0 @@
-package com.github.maracas.roseau.diff;
-
-import com.github.maracas.roseau.api.model.API;
-import com.github.maracas.roseau.api.model.ClassDecl;
-import com.github.maracas.roseau.api.model.ConstructorDecl;
-import com.github.maracas.roseau.api.model.ExecutableDecl;
-import com.github.maracas.roseau.api.model.FieldDecl;
-import com.github.maracas.roseau.api.model.FormalTypeParameter;
-import com.github.maracas.roseau.api.model.MethodDecl;
-import com.github.maracas.roseau.api.model.SourceLocation;
-import com.github.maracas.roseau.api.model.Symbol;
-import com.github.maracas.roseau.api.model.TypeDecl;
-import com.github.maracas.roseau.api.model.reference.ITypeReference;
-import com.github.maracas.roseau.api.model.reference.TypeReference;
-import com.github.maracas.roseau.diff.changes.BreakingChange;
-import com.github.maracas.roseau.diff.changes.BreakingChangeKind;
-
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * This class represents Roseau's comparison tool for detecting breaking changes between two API versions.
- */
-public class APIDiff {
- /**
- * The first version of the API to be compared.
- */
- private final API v1;
-
- /**
- * The second version of the API to be compared.
- */
- private final API v2;
-
- /**
- * List of all the breaking changes identified in the comparison.
- */
- private final List breakingChanges;
-
- /**
- * Constructs an APIDiff instance to compare two API versions for breaking changes detection.
- *
- * @param v1 The first version of the API to compare.
- * @param v2 The second version of the API to compare.
- */
- public APIDiff(API v1, API v2) {
- this.v1 = Objects.requireNonNull(v1);
- this.v2 = Objects.requireNonNull(v2);
- breakingChanges = new ArrayList<>();
- }
-
- public List diff() {
- v1.getExportedTypes().forEach(t1 -> {
- Optional findT2 = v2.getExportedType(t1.getQualifiedName());
-
- findT2.ifPresentOrElse(
- // There is a matching type
- t2 -> {
- diffType(t1, t2);
- diffFields(t1, t2);
- diffMethods(t1, t2);
- diffAddedMethods(t1, t2);
-
- if (t1 instanceof ClassDecl c1 && t2 instanceof ClassDecl c2)
- diffConstructors(c1, c2);
- },
- // Type has been removed
- () -> bc(BreakingChangeKind.TYPE_REMOVED, t1)
- );
- });
-
- return breakingChanges;
- }
-
- private void diffFields(TypeDecl t1, TypeDecl t2) {
- t1.getFields().forEach(f1 -> {
- Optional findF2 = t2.findField(f1.getSimpleName());
-
- findF2.ifPresentOrElse(
- // There is a matching field
- f2 -> diffField(f1, f2),
- // The field has been removed
- () -> bc(BreakingChangeKind.FIELD_REMOVED, f1)
- );
- });
- }
-
- private void diffMethods(TypeDecl t1, TypeDecl t2) {
- t1.getMethods().forEach(m1 -> {
- Optional matchM2 = t2.getMethods().stream()
- .filter(m -> m.hasSameSignature(m1))
- .findFirst();
-
- matchM2.ifPresentOrElse(
- // There is a matching method
- m2 -> diffMethod(t1, m1, m2),
- // The method has been removed
- () -> bc(BreakingChangeKind.METHOD_REMOVED, m1)
- );
- });
- }
-
- private void diffConstructors(ClassDecl c1, ClassDecl c2) {
- c1.getConstructors().forEach(cons1 -> {
- Optional matchCons2 = c2.getConstructors().stream()
- .filter(cons -> cons.hasSameSignature(cons1))
- .findFirst();
-
- matchCons2.ifPresentOrElse(
- // There is a matching constructor
- cons2 -> diffConstructor(cons1, cons2),
- // The constructor has been removed
- () -> bc(BreakingChangeKind.CONSTRUCTOR_REMOVED, cons1)
- );
- });
- }
-
- private void diffAddedMethods(TypeDecl t1, TypeDecl t2) {
- t2.getMethods().stream()
- .filter(m2 -> t1.getMethods().stream().noneMatch(m1 -> m1.hasSameSignature(m2)))
- .forEach(m2 -> {
- if (t2.isInterface() && !m2.isDefault())
- bc(BreakingChangeKind.METHOD_ADDED_TO_INTERFACE, t1);
-
- if (t2.isClass() && m2.isAbstract())
- bc(BreakingChangeKind.METHOD_ABSTRACT_ADDED_TO_CLASS, t1);
- });
- }
-
- private void diffType(TypeDecl t1, TypeDecl t2) {
- if (t1.isClass()) {
- if (!t1.isFinal() && t2.isFinal())
- bc(BreakingChangeKind.CLASS_NOW_FINAL, t1);
-
- if (!t1.isSealed() && t2.isSealed())
- bc(BreakingChangeKind.CLASS_NOW_FINAL, t1);
-
- if (!t1.isAbstract() && t2.isAbstract())
- bc(BreakingChangeKind.CLASS_NOW_ABSTRACT, t1);
-
- if (!t1.isStatic() && t2.isStatic() && t1.isNested() && t2.isNested())
- bc(BreakingChangeKind.NESTED_CLASS_NOW_STATIC, t1);
-
- if (t1.isStatic() && !t2.isStatic() && t1.isNested() && t2.isNested())
- bc(BreakingChangeKind.NESTED_CLASS_NO_LONGER_STATIC, t1);
-
- if (!t1.isCheckedException() && t2.isCheckedException())
- bc(BreakingChangeKind.CLASS_NOW_CHECKED_EXCEPTION, t1);
- }
-
- if (t1.isPublic() && t2.isProtected())
- bc(BreakingChangeKind.TYPE_NOW_PROTECTED, t1);
-
- if (t1 instanceof ClassDecl cls1 && t2 instanceof ClassDecl cls2) {
- if (cls1.getSuperClass().isPresent() && cls2.getSuperClass().isEmpty())
- bc(BreakingChangeKind.SUPERCLASS_MODIFIED_INCOMPATIBLE, t1);
- }
-
- // Deleted super-interfaces
- if (t1.getImplementedInterfaces().stream()
- .anyMatch(intf1 -> t2.getImplementedInterfaces().stream()
- .noneMatch(intf2 -> intf1.getQualifiedName().equals(intf2.getQualifiedName()))))
- bc(BreakingChangeKind.SUPERCLASS_MODIFIED_INCOMPATIBLE, t1);
-
- if (!t1.getClass().equals(t2.getClass()))
- bc(BreakingChangeKind.CLASS_TYPE_CHANGED, t1);
-
- int formalParametersCount1 = t1.getFormalTypeParameters().size();
- int formalParametersCount2 = t2.getFormalTypeParameters().size();
- if (formalParametersCount1 == formalParametersCount2) {
- for (int i = 0; i < formalParametersCount1; i++) {
- FormalTypeParameter p1 = t1.getFormalTypeParameters().get(i);
- FormalTypeParameter p2 = t2.getFormalTypeParameters().get(i);
-
- List bounds1 = p1.bounds().stream()
- .map(ITypeReference::getQualifiedName)
- .toList();
- List bounds2 = p2.bounds().stream()
- .map(ITypeReference::getQualifiedName)
- .toList();
-
- if (bounds1.size() != bounds2.size()
- || !(new HashSet<>(bounds1)).equals(new HashSet<>(bounds2))) {
- bc(BreakingChangeKind.TYPE_FORMAL_TYPE_PARAMETERS_CHANGED, t1);
- }
- }
- } else if (formalParametersCount1 < formalParametersCount2) {
- bc(BreakingChangeKind.TYPE_FORMAL_TYPE_PARAMETERS_REMOVED, t1);
- } else {
- bc(BreakingChangeKind.TYPE_FORMAL_TYPE_PARAMETERS_ADDED, t1);
- }
- }
-
- private void diffField(FieldDecl f1, FieldDecl f2) {
- if (!f1.isFinal() && f2.isFinal())
- bc(BreakingChangeKind.FIELD_NOW_FINAL, f1);
-
- if (!f1.isStatic() && f2.isStatic())
- bc(BreakingChangeKind.FIELD_NOW_STATIC, f1);
-
- if (f1.isStatic() && !f2.isStatic())
- bc(BreakingChangeKind.FIELD_NO_LONGER_STATIC, f1);
-
- if (!f1.getType().equals(f2.getType()))
- bc(BreakingChangeKind.FIELD_TYPE_CHANGED, f1);
-
- if (f1.isPublic() && f2.isProtected())
- bc(BreakingChangeKind.FIELD_LESS_ACCESSIBLE, f1);
- }
-
- private void diffMethod(TypeDecl t1, MethodDecl m1, MethodDecl m2) {
- if (!m1.isFinal() && m2.isFinal())
- bc(BreakingChangeKind.METHOD_NOW_FINAL, m1);
-
- if (!m1.isStatic() && m2.isStatic())
- bc(BreakingChangeKind.METHOD_NOW_STATIC, m1);
-
- if (!m1.isNative() && m2.isNative())
- bc(BreakingChangeKind.METHOD_NOW_NATIVE, m1);
-
- if (m1.isStatic() && !m2.isStatic())
- bc(BreakingChangeKind.METHOD_NO_LONGER_STATIC, m1);
-
- if (m1.isStrictFp() && !m2.isStrictFp())
- bc(BreakingChangeKind.METHOD_NO_LONGER_STRICTFP, m1);
-
- if (!m1.isAbstract() && m2.isAbstract())
- bc(BreakingChangeKind.METHOD_NOW_ABSTRACT, m1);
-
- if (m1.isAbstract() && m2.isDefault()) // Careful
- bc(BreakingChangeKind.METHOD_ABSTRACT_NOW_DEFAULT, m1);
-
- if (m1.isPublic() && m2.isProtected())
- bc(BreakingChangeKind.METHOD_LESS_ACCESSIBLE, m1);
-
- if (!m1.getType().equals(m2.getType()))
- bc(BreakingChangeKind.METHOD_RETURN_TYPE_CHANGED, m1);
-
- List> additionalExceptions1 = m1.getThrownExceptions().stream()
- .filter(e -> !m2.getThrownExceptions().contains(e))
- .toList();
-
- List> additionalExceptions2 = m2.getThrownExceptions().stream()
- .filter(e -> !m1.getThrownExceptions().contains(e))
- .toList();
-
- if (!additionalExceptions1.isEmpty())
- bc(BreakingChangeKind.METHOD_NO_LONGER_THROWS_CHECKED_EXCEPTION, m1);
-
- if (!additionalExceptions2.isEmpty())
- bc(BreakingChangeKind.METHOD_NOW_THROWS_CHECKED_EXCEPTION, m1);
-
- // JLS says only one vararg per method, in last position
- if (!m1.getParameters().isEmpty() && m1.getParameters().getLast().isVarargs()
- && (m2.getParameters().isEmpty() || !m2.getParameters().getLast().isVarargs()))
- bc(BreakingChangeKind.METHOD_NO_LONGER_VARARGS, m1);
-
- if (!m2.getParameters().isEmpty() && m2.getParameters().getLast().isVarargs()
- && (m1.getParameters().isEmpty() || !m1.getParameters().getLast().isVarargs()))
- bc(BreakingChangeKind.METHOD_NOW_VARARGS, m1);
-
- // FIXME: no checks for parameters???
-
- diffFormalTypeParameters(m1, m2);
- }
-
- private void diffConstructor(ConstructorDecl cons1, ConstructorDecl cons2) {
- if (cons1.isPublic() && cons2.isProtected())
- bc(BreakingChangeKind.CONSTRUCTOR_LESS_ACCESSIBLE, cons1);
-
- diffFormalTypeParameters(cons1, cons2);
- }
-
- private void diffFormalTypeParameters(ExecutableDecl e1, ExecutableDecl e2) {
- if (e1.getFormalTypeParameters().size() > e2.getFormalTypeParameters().size())
- bc(BreakingChangeKind.METHOD_FORMAL_TYPE_PARAMETERS_REMOVED, e1);
-
- if (e1.getFormalTypeParameters().size() < e2.getFormalTypeParameters().size())
- bc(BreakingChangeKind.METHOD_FORMAL_TYPE_PARAMETERS_ADDED, e1);
-
- for (int i = 0; i < e1.getFormalTypeParameters().size(); i++) {
- List bounds1 = e1.getFormalTypeParameters().get(i).bounds();
-
- if (i < e2.getFormalTypeParameters().size()) {
- List bounds2 = e2.getFormalTypeParameters().get(i).bounds();
-
- if (bounds1.size() != bounds2.size()
- || !new HashSet<>(bounds1).equals(new HashSet<>(bounds2)))
- bc(BreakingChangeKind.METHOD_FORMAL_TYPE_PARAMETERS_CHANGED, e1);
- }
- }
- }
-
- private void bc(BreakingChangeKind kind, Symbol impactedSymbol) {
- breakingChanges.add(new BreakingChange(kind, impactedSymbol));
- }
-
- /**
- * Retrieves the list of all the breaking changes detected between the two API versions.
- *
- * @return List of all the breaking changes
- */
- public List getBreakingChanges() {
- return breakingChanges;
- }
-
- /**
- * Generates a csv report for the detected breaking changes. This report includes the kind, type qualifiedName,
- *
- * position, associated element, and nature of each detected BC.
- */
- public void breakingChangesReport() throws IOException {
- try (FileWriter writer = new FileWriter("breaking_changes_report.csv")) {
- writer.write("Kind,Element,Nature,Position\n");
-
- for (BreakingChange breakingChange : breakingChanges) {
- String kind = breakingChange.kind().toString();
- String element = breakingChange.impactedSymbol().getQualifiedName();
- String nature = breakingChange.kind().getNature().toString();
- SourceLocation location = breakingChange.impactedSymbol().getLocation();
-
- writer.write(kind + "," + element + "," + nature + "," + location + "\n");
- }
- }
- }
-
- /**
- * Generates a string representation of the breaking changes list.
- *
- * @return A formatted string containing all the breaking changes and their info.
- */
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder();
- for (BreakingChange breakingChange : breakingChanges) {
- result.append(breakingChange.toString()).append("\n");
- result.append(" =========================\n\n");
- }
-
- return result.toString();
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChange.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChange.java
deleted file mode 100644
index 7929f2ef..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChange.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.github.maracas.roseau.diff.changes;
-
-import com.github.maracas.roseau.api.model.Symbol;
-
-/**
- * Represents a breaking change identified during the comparison of APIs between the two library versions.
- * This class encapsulates information about the breaking change's kind and impacted symbol.
- *
- * @param kind The kind of the breaking change.
- * @param impactedSymbol The element associated with the breaking change.
- */
-public record BreakingChange(
- BreakingChangeKind kind,
- Symbol impactedSymbol
-) {
- @Override
- public String toString() {
- return "BC[kind=%s, symbol=%s]".formatted(kind, impactedSymbol.getQualifiedName());
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeKind.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeKind.java
deleted file mode 100644
index 3d9362cb..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeKind.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.github.maracas.roseau.diff.changes;
-
-import static com.github.maracas.roseau.diff.changes.BreakingChangeNature.ADDITION;
-import static com.github.maracas.roseau.diff.changes.BreakingChangeNature.DELETION;
-import static com.github.maracas.roseau.diff.changes.BreakingChangeNature.MUTATION;
-
-/**
- * Enumerates the source and binary breaking changes taken into account.
- */
-public enum BreakingChangeKind {
- TYPE_REMOVED(DELETION),
- CLASS_NOW_ABSTRACT(MUTATION),
- CLASS_NOW_FINAL(MUTATION),
- NESTED_CLASS_NOW_STATIC(MUTATION),
- NESTED_CLASS_NO_LONGER_STATIC(MUTATION),
- CLASS_TYPE_CHANGED(MUTATION),
- CLASS_NOW_CHECKED_EXCEPTION(MUTATION),
- TYPE_NOW_PROTECTED(MUTATION),
- SUPERCLASS_MODIFIED_INCOMPATIBLE(MUTATION),
- TYPE_FORMAL_TYPE_PARAMETERS_ADDED(MUTATION),
- TYPE_FORMAL_TYPE_PARAMETERS_REMOVED(MUTATION),
- TYPE_FORMAL_TYPE_PARAMETERS_CHANGED(MUTATION),
-
- METHOD_REMOVED(DELETION),
- METHOD_LESS_ACCESSIBLE(MUTATION),
- METHOD_RETURN_TYPE_CHANGED(MUTATION),
- METHOD_NOW_ABSTRACT(MUTATION),
- METHOD_NOW_FINAL(MUTATION),
- METHOD_NOW_STATIC(MUTATION),
- METHOD_NOW_NATIVE(MUTATION),
- METHOD_NOW_VARARGS(MUTATION),
- METHOD_NO_LONGER_VARARGS(MUTATION),
- METHOD_NO_LONGER_STATIC(MUTATION),
- METHOD_NO_LONGER_STRICTFP(MUTATION),
- METHOD_ADDED_TO_INTERFACE(ADDITION),
- METHOD_NOW_THROWS_CHECKED_EXCEPTION(MUTATION),
- METHOD_NO_LONGER_THROWS_CHECKED_EXCEPTION(MUTATION),
- METHOD_ABSTRACT_ADDED_TO_CLASS(ADDITION),
- METHOD_ABSTRACT_NOW_DEFAULT(MUTATION),
- METHOD_FORMAL_TYPE_PARAMETERS_ADDED(MUTATION),
- METHOD_FORMAL_TYPE_PARAMETERS_REMOVED(MUTATION),
- METHOD_FORMAL_TYPE_PARAMETERS_CHANGED(MUTATION),
-
- FIELD_NOW_FINAL(MUTATION),
- FIELD_NOW_STATIC(MUTATION),
- FIELD_NO_LONGER_STATIC(MUTATION),
- FIELD_TYPE_CHANGED(MUTATION),
- FIELD_REMOVED(DELETION),
- FIELD_LESS_ACCESSIBLE(MUTATION),
-
- CONSTRUCTOR_REMOVED(DELETION),
- CONSTRUCTOR_LESS_ACCESSIBLE(MUTATION);
-
- // Do not make sense or unsupported or not implemented yet
- /*ANNOTATION_DEPRECATED_ADDED,
-
- TYPE_GENERICS_CHANGED,
-
- METHOD_LESS_ACCESSIBLE_THAN_IN_SUPERCLASS,
- METHOD_IS_STATIC_AND_OVERRIDES_NOT_STATIC,
- METHOD_IS_NOT_STATIC_AND_OVERRIDES_STATIC,
- METHOD_RETURN_TYPE_GENERICS_CHANGED,
- METHOD_PARAMETER_GENERICS_CHANGED,
- METHOD_NEW_DEFAULT,
- METHOD_MOVED_TO_SUPERCLASS,
-
- FIELD_STATIC_AND_OVERRIDES_NON_STATIC,
- FIELD_NON_STATIC_AND_OVERRIDES_STATIC,
- FIELD_LESS_ACCESSIBLE_THAN_IN_SUPERCLASS,
- FIELD_GENERICS_CHANGED,
-
- CONSTRUCTOR_PARAMS_GENERICS_CHANGED,
- CONSTRUCTOR_GENERICS_CHANGED,
- CONSTRUCTOR_FORMAL_TYPE_PARAMETERS_CHANGED,
- CONSTRUCTOR_FORMAL_TYPE_PARAMETERS_ADDED,
- CONSTRUCTOR_FORMAL_TYPE_PARAMETERS_REMOVED;*/
-
- private final BreakingChangeNature nature;
-
- BreakingChangeKind(BreakingChangeNature nature) {
- this.nature = nature;
- }
-
- public BreakingChangeNature getNature() {
- return nature;
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeNature.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeNature.java
deleted file mode 100644
index 7da6ce52..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeNature.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.github.maracas.roseau.diff.changes;
-
-/**
- * Enumerates the three possible natures of a breaking change: ADDITION, MUTATION, and DELETION.
- */
-public enum BreakingChangeNature {
- /**
- * Indicates that the breaking change is a result of an addition to the API.
- */
- ADDITION,
-
- /**
- * Indicates that the breaking change results from an alteration of existing elements within the API.
- */
- MUTATION,
-
- /**
- * Indicates that the breaking change is a result of a deletion from the API.
- */
- DELETION
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/package-info.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/package-info.java
deleted file mode 100644
index a775b2ef..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * This package contains the classes and types needed for API comparisons and breaking changes detection.
- */
-package com.github.maracas.roseau.diff.changes;
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/package-info.java b/ConfGen/src/main/java/com/github/maracas/roseau/package-info.java
deleted file mode 100644
index d5a13a68..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/package-info.java
+++ /dev/null
@@ -1,5 +0,0 @@
-/**
- * This package contains the main API extraction and comparison tools.
- */
-
-package com.github.maracas.roseau;
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/visit/APIAlgebra.java b/ConfGen/src/main/java/com/github/maracas/roseau/visit/APIAlgebra.java
deleted file mode 100644
index b330c781..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/visit/APIAlgebra.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.github.maracas.roseau.visit;
-
-import com.github.maracas.roseau.api.model.API;
-import com.github.maracas.roseau.api.model.AnnotationDecl;
-import com.github.maracas.roseau.api.model.ClassDecl;
-import com.github.maracas.roseau.api.model.ConstructorDecl;
-import com.github.maracas.roseau.api.model.EnumDecl;
-import com.github.maracas.roseau.api.model.FieldDecl;
-import com.github.maracas.roseau.api.model.InterfaceDecl;
-import com.github.maracas.roseau.api.model.MethodDecl;
-import com.github.maracas.roseau.api.model.ParameterDecl;
-import com.github.maracas.roseau.api.model.RecordDecl;
-import com.github.maracas.roseau.api.model.Symbol;
-import com.github.maracas.roseau.api.model.TypeDecl;
-import com.github.maracas.roseau.api.model.reference.ArrayTypeReference;
-import com.github.maracas.roseau.api.model.reference.ITypeReference;
-import com.github.maracas.roseau.api.model.reference.PrimitiveTypeReference;
-import com.github.maracas.roseau.api.model.reference.TypeParameterReference;
-import com.github.maracas.roseau.api.model.reference.TypeReference;
-
-public interface APIAlgebra {
- T api(API it);
- T classDecl(ClassDecl it);
- T interfaceDecl(InterfaceDecl it);
- T enumDecl(EnumDecl it);
- T annotationDecl(AnnotationDecl it);
- T recordDecl(RecordDecl it);
- T methodDecl(MethodDecl it);
- T constructorDecl(ConstructorDecl it);
- T fieldDecl(FieldDecl it);
- T parameterDecl(ParameterDecl it);
- T typeReference(TypeReference it);
- T primitiveTypeReference(PrimitiveTypeReference it);
- T arrayTypeReference(ArrayTypeReference it);
- T typeParameterReference(TypeParameterReference it);
-
- default T $(API it) {
- return api(it);
- }
-
- default T $(Symbol it) {
- return switch (it) {
- case RecordDecl r -> recordDecl(r);
- case EnumDecl e -> enumDecl(e);
- case ClassDecl c -> classDecl(c);
- case InterfaceDecl i -> interfaceDecl(i);
- case AnnotationDecl a -> annotationDecl(a);
- case MethodDecl m -> methodDecl(m);
- case ConstructorDecl c -> constructorDecl(c);
- case FieldDecl f -> fieldDecl(f);
- };
- }
-
- default T $(ParameterDecl it) {
- return parameterDecl(it);
- }
-
- default T $(ITypeReference it) {
- return switch (it) {
- case TypeReference> typeRef -> typeReference(typeRef);
- case PrimitiveTypeReference primitiveRef -> primitiveTypeReference(primitiveRef);
- case ArrayTypeReference arrayRef -> arrayTypeReference(arrayRef);
- case TypeParameterReference tpRef -> typeParameterReference(tpRef);
- };
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/visit/AbstractAPIVisitor.java b/ConfGen/src/main/java/com/github/maracas/roseau/visit/AbstractAPIVisitor.java
deleted file mode 100644
index 0d8d741d..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/visit/AbstractAPIVisitor.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package com.github.maracas.roseau.visit;
-
-import com.github.maracas.roseau.api.model.API;
-import com.github.maracas.roseau.api.model.AnnotationDecl;
-import com.github.maracas.roseau.api.model.ClassDecl;
-import com.github.maracas.roseau.api.model.ConstructorDecl;
-import com.github.maracas.roseau.api.model.EnumDecl;
-import com.github.maracas.roseau.api.model.ExecutableDecl;
-import com.github.maracas.roseau.api.model.FieldDecl;
-import com.github.maracas.roseau.api.model.InterfaceDecl;
-import com.github.maracas.roseau.api.model.MethodDecl;
-import com.github.maracas.roseau.api.model.ParameterDecl;
-import com.github.maracas.roseau.api.model.RecordDecl;
-import com.github.maracas.roseau.api.model.Symbol;
-import com.github.maracas.roseau.api.model.TypeDecl;
-import com.github.maracas.roseau.api.model.reference.ArrayTypeReference;
-import com.github.maracas.roseau.api.model.reference.PrimitiveTypeReference;
-import com.github.maracas.roseau.api.model.reference.TypeParameterReference;
-import com.github.maracas.roseau.api.model.reference.TypeReference;
-
-public class AbstractAPIVisitor implements APIAlgebra {
- public Visit api(API it) {
- return () -> it.getAllTypes().forEach(t -> $(t).visit());
- }
-
- @Override
- public Visit classDecl(ClassDecl it) {
- return () -> {
- typeDecl(it).visit();
- it.getSuperClass().ifPresent(sup -> $(sup).visit());
- it.getConstructors().forEach(cons -> $(cons).visit());
- };
- }
-
- @Override
- public Visit interfaceDecl(InterfaceDecl it) {
- return typeDecl(it);
- }
-
- @Override
- public Visit enumDecl(EnumDecl it) {
- return classDecl(it);
- }
-
- @Override
- public Visit annotationDecl(AnnotationDecl it) {
- return typeDecl(it);
- }
-
- @Override
- public Visit recordDecl(RecordDecl it) {
- return classDecl(it);
- }
-
- @Override
- public Visit methodDecl(MethodDecl it) {
- return executableDecl(it);
- }
-
- @Override
- public Visit constructorDecl(ConstructorDecl it) {
- return executableDecl(it);
- }
-
- @Override
- public Visit fieldDecl(FieldDecl it) {
- return () -> {
- symbol(it).visit();
- if (it.getType() != null)
- $(it.getType()).visit();
- };
- }
-
- @Override
- public Visit parameterDecl(ParameterDecl it) {
- return () -> {
- if (it.type() != null)
- $(it.type()).visit();
- };
- }
-
- @Override
- public Visit typeReference(TypeReference it) {
- return () -> {};
- }
-
- @Override
- public Visit primitiveTypeReference(PrimitiveTypeReference it) {
- return () -> {};
- }
-
- @Override
- public Visit arrayTypeReference(ArrayTypeReference it) {
- return () -> {};
- }
-
- @Override
- public Visit typeParameterReference(TypeParameterReference it) {
- return () -> {};
- }
-
- public Visit symbol(Symbol it) {
- return () -> {};
- }
-
- public Visit typeDecl(TypeDecl it) {
- return () -> {
- symbol(it).visit();
- it.getImplementedInterfaces().forEach(intf -> $(intf).visit());
- it.getFields().forEach(field -> $(field).visit());
- it.getMethods().forEach(meth -> $(meth).visit());
- };
- }
-
- public Visit executableDecl(ExecutableDecl it) {
- return () -> {
- symbol(it).visit();
- if (it.getType() != null)
- $(it.getType()).visit();
- it.getParameters().forEach(p -> $(p).visit());
- it.getThrownExceptions().forEach(e -> $(e).visit());
- };
- }
-}
diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/visit/Visit.java b/ConfGen/src/main/java/com/github/maracas/roseau/visit/Visit.java
deleted file mode 100644
index 317b4f89..00000000
--- a/ConfGen/src/main/java/com/github/maracas/roseau/visit/Visit.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.github.maracas.roseau.visit;
-
-@FunctionalInterface
-public interface Visit {
- void visit();
-}
diff --git a/Samples/sampleclient/.gitignore b/ConfGenCoverage/.gitignore
similarity index 100%
rename from Samples/sampleclient/.gitignore
rename to ConfGenCoverage/.gitignore
diff --git a/ConfGenCoverage/build.gradle b/ConfGenCoverage/build.gradle
new file mode 100644
index 00000000..cd077321
--- /dev/null
+++ b/ConfGenCoverage/build.gradle
@@ -0,0 +1,81 @@
+plugins {
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '8.1.1'
+}
+
+group 'com.github.gilesi.confgencoverage'
+version '1.0-SNAPSHOT'
+
+
+compileJava {
+ options.encoding = 'UTF-8'
+}
+
+tasks.withType(JavaCompile).configureEach {
+ options.encoding = 'UTF-8'
+}
+
+repositories {
+ mavenCentral()
+ //mavenLocal()
+ maven {
+ name = "IntelliJ"
+ url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies'
+ }
+ maven {
+ name = "Roseau"
+ url = "https://maven.pkg.github.com/alien-tools/roseau"
+ credentials {
+ username = project.findProperty("gpr.user")
+ password = project.findProperty("gpr.key")
+ }
+ }
+}
+
+dependencies {
+ implementation 'com.github.maracas:roseau:0.0.2-SNAPSHOT'
+ implementation 'com.thoughtworks.xstream:xstream:1.4.20'
+}
+
+jar {
+ manifest {
+ attributes 'Main-Class': 'com.github.gilesi.confgencoverage.Main',
+ 'Multi-Release': 'true'
+ }
+}
+
+application {
+ mainClass = 'com.github.gilesi.confgencoverage.Main'
+}
+
+description = 'Main distribution.'
+
+shadowJar {
+ archiveBaseName.set('com.github.gilesi.confgencoverage')
+ archiveClassifier.set('')
+ archiveVersion.set('')
+ mergeServiceFiles()
+}
+
+distributions {
+ shadow {
+ distributionBaseName = 'com.github.gilesi.confgencoverage'
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea {
+ module {
+ downloadJavadoc = true
+ downloadSources = true
+ }
+}
+
+run {
+ jvmArgs = [
+ "-XX:InitialHeapSize=2G",
+ "-XX:MaxHeapSize=2G"
+ ]
+}
diff --git a/Samples/gradle/wrapper/gradle-wrapper.jar b/ConfGenCoverage/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from Samples/gradle/wrapper/gradle-wrapper.jar
rename to ConfGenCoverage/gradle/wrapper/gradle-wrapper.jar
diff --git a/Samples/gradle/wrapper/gradle-wrapper.properties b/ConfGenCoverage/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from Samples/gradle/wrapper/gradle-wrapper.properties
rename to ConfGenCoverage/gradle/wrapper/gradle-wrapper.properties
diff --git a/Samples/gradlew b/ConfGenCoverage/gradlew
old mode 100644
new mode 100755
similarity index 100%
rename from Samples/gradlew
rename to ConfGenCoverage/gradlew
diff --git a/Samples/gradlew.bat b/ConfGenCoverage/gradlew.bat
similarity index 100%
rename from Samples/gradlew.bat
rename to ConfGenCoverage/gradlew.bat
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/CodeType.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/CodeType.java
new file mode 100644
index 00000000..f4f0594e
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/CodeType.java
@@ -0,0 +1,11 @@
+package com.github.gilesi.confgencoverage;
+
+import java.util.EnumSet;
+
+public enum CodeType {
+ MAIN,
+ TEST,
+ SAMPLE;
+
+ public static final EnumSet ALL = EnumSet.allOf(CodeType.class);
+}
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/Main.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/Main.java
new file mode 100644
index 00000000..c32b23a9
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/Main.java
@@ -0,0 +1,177 @@
+package com.github.gilesi.confgencoverage;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.github.gilesi.confgencoverage.exceptions.UnsupportedJavaPrimitiveTypeException;
+import com.github.gilesi.confgencoverage.models.Class;
+import com.github.gilesi.confgencoverage.models.InstrumentationParameters;
+import com.github.gilesi.confgencoverage.models.Method;
+import com.github.gilesi.confgencoverage.spoon.SpoonLauncherUtilities;
+import com.github.maracas.roseau.api.SpoonAPIExtractor;
+import com.github.maracas.roseau.api.model.*;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+import spoon.Launcher;
+import spoon.reflect.CtModel;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.*;
+
+public class Main {
+ private static final ObjectMapper objectMapper = new ObjectMapper()
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL)
+ .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+ .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES)
+ .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS);
+
+ private static String getClassNameFromFQN(String fullyQualifiedName) {
+ String parameterLessQualifiedName = fullyQualifiedName;
+
+ if (fullyQualifiedName.contains("(")) {
+ parameterLessQualifiedName = fullyQualifiedName.split("\\(")[0];
+ }
+
+ String[] nameElements = parameterLessQualifiedName.split("\\.");
+
+ return String.join(".", Arrays.stream(nameElements).limit(nameElements.length - 1).toArray(String[]::new));
+ }
+
+ private static String getMethodNameFromFQN(String fullyQualifiedName) {
+ String parameterLessQualifiedName = fullyQualifiedName;
+
+ if (fullyQualifiedName.contains("(")) {
+ parameterLessQualifiedName = fullyQualifiedName.split("\\(")[0];
+ }
+
+ String[] nameElements = parameterLessQualifiedName.split("\\.");
+
+ return nameElements[nameElements.length - 1];
+ }
+
+ private static void addToInstrumentationObject(InstrumentationParameters instrumentationParameters, String className, String methodName, String methodDescriptor) {
+ for (Class classToInstrument : instrumentationParameters.InstrumentedAPIClasses) {
+ if (classToInstrument.ClassName.equals(className)) {
+ for (Method methodParameter : classToInstrument.ClassMethods) {
+ if (methodParameter.MethodName.equals(methodName)) {
+ for (String descriptorToInstrument : methodParameter.MethodDescriptors) {
+ if (descriptorToInstrument.equals(methodDescriptor)) {
+ // Element is already in the config, return now
+ return;
+ }
+ }
+
+ // Element doesn't have a descriptor in the config, add now
+ methodParameter.MethodDescriptors.add(methodDescriptor);
+ return;
+ }
+ }
+
+ // Element doesn't have a method in the config, add now
+ Method methodParameter = new Method();
+ methodParameter.MethodName = methodName;
+ methodParameter.MethodDescriptors = new ArrayList<>();
+ methodParameter.MethodDescriptors.add(methodDescriptor);
+
+ classToInstrument.ClassMethods.add(methodParameter);
+ return;
+ }
+ }
+
+ // Element doesn't have a class in the config, add now
+ Class classParameter = new Class();
+ classParameter.ClassName = className;
+ classParameter.ClassMethods = new ArrayList<>();
+ classParameter.ClassDescriptors = new ArrayList<>();
+ Method methodParameter = new Method();
+ methodParameter.MethodName = methodName;
+ methodParameter.MethodDescriptors = new ArrayList<>();
+ methodParameter.MethodDescriptors.add(methodDescriptor);
+ classParameter.ClassMethods.add(methodParameter);
+
+ instrumentationParameters.InstrumentedAPIClasses.add(classParameter);
+ }
+
+ private static void addToInstrumentationObject(InstrumentationParameters instrumentationParameters, String className, String constructorDescriptor) {
+ for (Class classToInstrument : instrumentationParameters.InstrumentedAPIClasses) {
+ if (classToInstrument.ClassName.equals(className)) {
+ for (String descriptorToInstrument : classToInstrument.ClassDescriptors) {
+ if (descriptorToInstrument.equals(constructorDescriptor)) {
+ // Element is already in the config, return now
+ return;
+ }
+
+ // Element doesn't have a descriptor in the config, add now
+ classToInstrument.ClassDescriptors.add(constructorDescriptor);
+ return;
+ }
+ }
+ }
+
+ // Element doesn't have a class in the config, add now
+ Class classParameter = new Class();
+ classParameter.ClassName = className;
+ classParameter.ClassMethods = new ArrayList<>();
+ classParameter.ClassDescriptors = new ArrayList<>();
+ classParameter.ClassDescriptors.add(constructorDescriptor);
+
+ instrumentationParameters.InstrumentedAPIClasses.add(classParameter);
+ }
+
+ public static void main(String[] args) throws UnsupportedJavaPrimitiveTypeException, IOException {
+ String apiReportOutputLocation = args[0];
+ String traceOutputLocation = args[1];
+ String libraryProjectLocation = args[2];
+
+ Path apiReportOutputPath = Path.of(apiReportOutputLocation);
+
+ System.out.println("Processing Library Project...");
+
+ Launcher libraryLauncher = SpoonLauncherUtilities.getCommonLauncherInstance();
+ SpoonLauncherUtilities.applyProjectToLauncher(libraryLauncher, Path.of(libraryProjectLocation), EnumSet.of(CodeType.MAIN));
+ CtModel libraryModel = libraryLauncher.buildModel();
+
+ // API model for libraries
+ API libraryApiModel = new SpoonAPIExtractor().extractAPI(libraryModel);
+
+ InstrumentationParameters instrumentationParameters = new InstrumentationParameters();
+ instrumentationParameters.InstrumentedAPIClasses = new ArrayList<>();
+ instrumentationParameters.traceOutputLocation = traceOutputLocation;
+
+ for (ClassDecl classDecl : libraryApiModel.getExportedTypes()
+ .filter(ClassDecl.class::isInstance)
+ .map(ClassDecl.class::cast).toList()) {
+
+ for (ConstructorDecl constructor : classDecl.getConstructors()) {
+ String className = getClassNameFromFQN(constructor.getQualifiedName());
+ String methodDescriptor = RoseauDescriptor.getDescriptor(constructor, false);
+
+ addToInstrumentationObject(instrumentationParameters, className, methodDescriptor);
+ }
+
+ for (MethodDecl method : classDecl.getAllMethods().toList()) {
+ String FQN = method.getQualifiedName();
+ String className = getClassNameFromFQN(FQN);
+ String methodName = getMethodNameFromFQN(FQN);
+ String methodDescriptor = RoseauDescriptor.getDescriptor(method, false);
+
+ addToInstrumentationObject(instrumentationParameters, className, methodName, methodDescriptor);
+ }
+ }
+
+ BufferedWriter bufferedWriter = Files.newBufferedWriter(apiReportOutputPath, StandardOpenOption.CREATE);
+
+ try {
+ String serializedJsonString = objectMapper.writeValueAsString(instrumentationParameters);
+ bufferedWriter.write(serializedJsonString);
+ } catch (Exception e) {
+ System.out.println(String.format("ERROR: Cannot serialize specific argument: %s", e));
+ }
+
+ bufferedWriter.close();
+ }
+}
\ No newline at end of file
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/RoseauDescriptor.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/RoseauDescriptor.java
new file mode 100644
index 00000000..19be6166
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/RoseauDescriptor.java
@@ -0,0 +1,88 @@
+package com.github.gilesi.confgencoverage;
+
+import com.github.gilesi.confgencoverage.exceptions.UnsupportedJavaPrimitiveTypeException;
+import com.github.maracas.roseau.api.model.ConstructorDecl;
+import com.github.maracas.roseau.api.model.MethodDecl;
+import com.github.maracas.roseau.api.model.ParameterDecl;
+import com.github.maracas.roseau.api.model.reference.*;
+
+import java.util.List;
+
+public class RoseauDescriptor {
+ public static String getDescriptor(ConstructorDecl constructorDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException {
+ StringBuilder descriptor = new StringBuilder().append('(');
+ for (ParameterDecl parameterType : constructorDecl.getParameters()) {
+ descriptor.append(getDescriptor(parameterType, handleTypeArgs));
+ }
+ return descriptor.append(")V").toString();
+ }
+
+ public static String getDescriptor(MethodDecl methodDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException {
+ StringBuilder descriptor = new StringBuilder().append('(');
+ for (ParameterDecl parameterType : methodDecl.getParameters()) {
+ descriptor.append(getDescriptor(parameterType, handleTypeArgs));
+ }
+ return descriptor.append(')').append(getDescriptor(methodDecl.getType(), handleTypeArgs)).toString();
+ }
+
+ public static String getDescriptor(ParameterDecl parameterDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException {
+ return getDescriptor(parameterDecl.type(), handleTypeArgs);
+ }
+
+ public static String getDescriptor(ITypeReference iTypeReference, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException {
+ switch (iTypeReference) {
+ case ArrayTypeReference arrayTypeReference -> {
+ String rest = getDescriptor(arrayTypeReference.componentType(), handleTypeArgs);
+ for (int i = 0; i < arrayTypeReference.dimension(); i++) {
+ rest = "[" + rest;
+ }
+ return rest;
+ }
+ case PrimitiveTypeReference primitiveTypeReference -> {
+ String name = primitiveTypeReference.getQualifiedName();
+ return switch (name) {
+ case "int" -> "I";
+ case "void" -> "V";
+ case "boolean" -> "Z";
+ case "byte" -> "B";
+ case "char" -> "C";
+ case "short" -> "S";
+ case "double" -> "D";
+ case "float" -> "F";
+ case "long" -> "J";
+ default -> throw new UnsupportedJavaPrimitiveTypeException(name);
+ };
+ }
+ case TypeParameterReference ignored -> {
+ //String name = typeParameterReference.getQualifiedName();
+ //return "L" + name.replace('.', '/') + ";";
+ // That's going to be U getSmething() in the JVM, so return object.
+ return "Ljava/lang/Object;";
+ }
+ case WildcardTypeReference ignored -> {
+ //String name = typeDecl.getQualifiedName();
+ //return name;
+ // That's going to be U getSmething() in the JVM, so return object.
+ return "Ljava/lang/Object;";
+ }
+ case TypeReference> typeReference -> {
+ String typeArgumentString = "";
+ if (handleTypeArgs) {
+ List typeArgs = typeReference.getTypeArguments();
+ if (!typeArgs.isEmpty()) {
+ // Check join string here to be very sure against spec and test it
+ typeArgumentString = "<%s>".formatted(String.join("", typeArgs.stream().map(t -> {
+ try {
+ return getDescriptor(t, true);
+ } catch (UnsupportedJavaPrimitiveTypeException e) {
+ throw new RuntimeException(e);
+ }
+ }).toList()));
+ }
+ }
+ String name = typeReference.getQualifiedName();
+ return "L%s%s;".formatted(name.replace('.', '/'), typeArgumentString);
+ }
+ }
+ }
+}
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/exceptions/UnsupportedJavaPrimitiveTypeException.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/exceptions/UnsupportedJavaPrimitiveTypeException.java
new file mode 100644
index 00000000..f324c951
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/exceptions/UnsupportedJavaPrimitiveTypeException.java
@@ -0,0 +1,7 @@
+package com.github.gilesi.confgencoverage.exceptions;
+
+public class UnsupportedJavaPrimitiveTypeException extends Exception {
+ public UnsupportedJavaPrimitiveTypeException(String primitive) {
+ super("Unsupported primitive type: %s".formatted(primitive));
+ }
+}
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java
new file mode 100644
index 00000000..934695c9
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java
@@ -0,0 +1,9 @@
+package com.github.gilesi.confgencoverage.models;
+
+import java.util.List;
+
+public class Class {
+ public String ClassName;
+ public List ClassMethods;
+ public List ClassDescriptors;
+}
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/InstrumentationParameters.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/InstrumentationParameters.java
new file mode 100644
index 00000000..ddac58e6
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/InstrumentationParameters.java
@@ -0,0 +1,8 @@
+package com.github.gilesi.confgencoverage.models;
+
+import java.util.List;
+
+public class InstrumentationParameters {
+ public List InstrumentedAPIClasses;
+ public String traceOutputLocation;
+}
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java
new file mode 100644
index 00000000..1c7ddc75
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java
@@ -0,0 +1,8 @@
+package com.github.gilesi.confgencoverage.models;
+
+import java.util.List;
+
+public class Method {
+ public String MethodName;
+ public List MethodDescriptors;
+}
\ No newline at end of file
diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/spoon/SpoonLauncherUtilities.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/spoon/SpoonLauncherUtilities.java
new file mode 100644
index 00000000..ab7ef5ab
--- /dev/null
+++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/spoon/SpoonLauncherUtilities.java
@@ -0,0 +1,222 @@
+package com.github.gilesi.confgencoverage.spoon;
+
+import spoon.Launcher;
+import spoon.MavenLauncher;
+import spoon.SpoonException;
+import spoon.support.StandardEnvironment;
+import spoon.support.compiler.SpoonPom;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.InvalidParameterException;
+import java.util.*;
+import java.util.regex.Pattern;
+
+import com.github.gilesi.confgencoverage.CodeType;
+
+public class SpoonLauncherUtilities {
+ public static Launcher getCommonLauncherInstance() {
+ Launcher launcher = new Launcher();
+
+ // Ignore missing types/classpath related errors
+ launcher.getEnvironment().setNoClasspath(true);
+ // Proceed even if we find the same type twice; affects the precision of the result
+ launcher.getEnvironment().setIgnoreDuplicateDeclarations(true);
+ // Ignore files with syntax/JLS violations and proceed
+ launcher.getEnvironment().setIgnoreSyntaxErrors(true);
+ // Ignore comments
+ launcher.getEnvironment().setCommentEnabled(false);
+ // Set Java version
+ // Note: even when using the MavenLauncher, it's sometimes not properly inferred, better be safe
+ launcher.getEnvironment().setComplianceLevel(17);
+
+ return launcher;
+ }
+
+ private static Collection getPomProjectPaths(String mavenProject, spoon.MavenLauncher.SOURCE_TYPE sourceType) throws SpoonException {
+ SpoonPom model;
+ ArrayList paths = new ArrayList<>();
+
+ File mavenProjectFile = new File(mavenProject);
+ if (!mavenProjectFile.exists()) {
+ throw new SpoonException("%s does not exist.".formatted(mavenProject));
+ }
+
+ Pattern profileFilter = Pattern.compile("^$");
+
+ try {
+ model = new SpoonPom(mavenProject, sourceType, new StandardEnvironment(), profileFilter);
+ } catch (Exception e) {
+ throw new SpoonException("Unable to read the pom", e);
+ }
+
+ // app source
+ if (spoon.MavenLauncher.SOURCE_TYPE.APP_SOURCE == sourceType || spoon.MavenLauncher.SOURCE_TYPE.ALL_SOURCE == sourceType) {
+ List sourceDirectories = model.getSourceDirectories();
+ for (File sourceDirectory : sourceDirectories) {
+ System.out.printf("Detected Project MAIN Source Directory at: %s%n", sourceDirectory);
+ paths.add(sourceDirectory.toPath());
+ }
+ }
+
+ // test source
+ if (spoon.MavenLauncher.SOURCE_TYPE.TEST_SOURCE == sourceType || spoon.MavenLauncher.SOURCE_TYPE.ALL_SOURCE == sourceType) {
+ List testSourceDirectories = model.getTestDirectories();
+ for (File sourceDirectory : testSourceDirectories) {
+ System.out.printf("Detected Project TEST Source Directory at: %s%n", sourceDirectory);
+ paths.add(sourceDirectory.toPath());
+ }
+ }
+
+ return paths;
+ }
+
+ private static int getPomProjectSourceComplianceLevel(String mavenProject) throws SpoonException {
+ SpoonPom model;
+
+ File mavenProjectFile = new File(mavenProject);
+ if (!mavenProjectFile.exists()) {
+ throw new SpoonException("%s does not exist.".formatted(mavenProject));
+ }
+
+ Pattern profileFilter = Pattern.compile("^$");
+
+ try {
+ model = new SpoonPom(mavenProject, MavenLauncher.SOURCE_TYPE.ALL_SOURCE, new StandardEnvironment(), profileFilter);
+ } catch (Exception e) {
+ throw new SpoonException("Unable to read the pom", e);
+ }
+
+ return model.getSourceVersion();
+ }
+
+ public static int getProjectSourceComplianceLevel(Path location) {
+ try {
+ return getPomProjectSourceComplianceLevel(location.toAbsolutePath().toString());
+ } catch (Exception ignore) {
+ System.out.println("Unable to get project source compliance level. %s".formatted(ignore));
+ }
+
+ return 11;
+ }
+
+ private static Path getPossibleSamplePath(Path location) {
+ Path examplesPath = location.resolve("examples");
+ Path samplesPath = location.resolve("samples");
+ Path srcPath = location.resolve("src");
+
+ if (Files.exists(examplesPath)) {
+ return examplesPath;
+ }
+
+ if (Files.exists(samplesPath)) {
+ return samplesPath;
+ }
+
+ if (Files.exists(srcPath)) {
+ return getPossibleSamplePath(srcPath);
+ }
+
+ return null;
+ }
+
+ private static Path getPossibleMainPath(Path location) {
+ Path mainPath = location.resolve("main");
+ Path srcPath = location.resolve("src");
+
+ if (Files.exists(mainPath)) {
+ return mainPath;
+ }
+
+ if (Files.exists(srcPath)) {
+ return getPossibleMainPath(srcPath);
+ }
+
+ return null;
+ }
+
+ private static Path getPossibleTestPath(Path location) {
+ Path testPath = location.resolve("test");
+ Path srcPath = location.resolve("src");
+
+ if (Files.exists(testPath)) {
+ return testPath;
+ }
+
+ if (Files.exists(srcPath)) {
+ return getPossibleTestPath(srcPath);
+ }
+
+ return null;
+ }
+
+ private static MavenLauncher.SOURCE_TYPE getSourceTypeValue(EnumSet codeTypes) throws InvalidParameterException {
+ if (codeTypes.contains(CodeType.MAIN) && codeTypes.contains(CodeType.TEST)) {
+ return MavenLauncher.SOURCE_TYPE.ALL_SOURCE;
+ } else if (codeTypes.contains(CodeType.MAIN)) {
+ return MavenLauncher.SOURCE_TYPE.APP_SOURCE;
+ } else if (codeTypes.contains(CodeType.TEST)) {
+ return MavenLauncher.SOURCE_TYPE.TEST_SOURCE;
+ } else {
+ throw new InvalidParameterException();
+ }
+ }
+
+ public static void applyProjectToLauncher(Launcher launcher, Path projectLocation, EnumSet codeTypes) {
+ launcher.getEnvironment().setComplianceLevel(getProjectSourceComplianceLevel(projectLocation));
+
+ Collection paths = getProjectPaths(projectLocation, codeTypes);
+ for (Path path : paths) {
+ launcher.addInputResource(path.toAbsolutePath().toString());
+ }
+ }
+
+ public static Collection getProjectPaths(Path projectLocation, EnumSet codeTypes) {
+ Collection paths = new HashSet<>();
+
+ System.out.printf("Trying to detect source directories for project: %s with types: %s%n", projectLocation, codeTypes);
+
+ if (codeTypes.contains(CodeType.MAIN) || codeTypes.contains(CodeType.TEST)) {
+ try {
+ MavenLauncher.SOURCE_TYPE sourceType = getSourceTypeValue(codeTypes);
+ paths.addAll(getPomProjectPaths(projectLocation.toString(), sourceType));
+ } catch (Exception ignored) {
+ System.out.println("Unable to get project code %s".formatted(ignored));
+ System.out.println("WARNING: Falling back to manual detection of project source paths because no maven pom could be parsed for the passed project");
+ if (codeTypes.contains(CodeType.MAIN)) {
+ Path mainPath = getPossibleMainPath(projectLocation);
+ if (mainPath != null) {
+ System.out.printf("Detected Project MAIN Source Directory at: %s%n", mainPath);
+ paths.add(mainPath);
+ }
+ }
+
+ if (codeTypes.contains(CodeType.TEST)) {
+ Path testPath = getPossibleTestPath(projectLocation);
+ if (testPath != null) {
+ System.out.printf("Detected Project TEST Source Directory at: %s%n", testPath);
+ paths.add(testPath);
+ }
+ }
+ }
+ }
+
+ if (codeTypes.contains(CodeType.SAMPLE)) {
+ Path samplePath = getPossibleSamplePath(projectLocation);
+ if (samplePath != null) {
+ System.out.printf("Detected Project SAMPLE Source Directory at: %s%n", samplePath);
+ paths.add(samplePath);
+ }
+ }
+
+ if (paths.isEmpty()) {
+ System.out.println("WARNING: Adding the entire directory because no project got detected and CodeTypes = ALL!");
+ paths.add(projectLocation);
+ }
+
+ System.out.printf("Finished detecting source directories for project: %s%n", projectLocation);
+
+ return paths;
+ }
+}
diff --git a/Samples/samplelibrary/.gitignore b/Coverage/.gitignore
similarity index 100%
rename from Samples/samplelibrary/.gitignore
rename to Coverage/.gitignore
diff --git a/Coverage/build.gradle b/Coverage/build.gradle
new file mode 100644
index 00000000..130749be
--- /dev/null
+++ b/Coverage/build.gradle
@@ -0,0 +1,78 @@
+plugins {
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '8.1.1'
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+group = 'com.github.gilesi.coverage'
+version = '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'net.bytebuddy:byte-buddy:1.15.10'
+ implementation 'net.bytebuddy:byte-buddy-agent:1.15.8'
+ implementation 'com.thoughtworks.xstream:xstream:1.4.20'
+ implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2'
+}
+
+jar {
+ manifest {
+ attributes 'Premain-Class': 'com.github.gilesi.coverage.Agent',
+ 'Agent-Class': 'com.github.gilesi.coverage.Agent',
+ 'Launcher-Agent-Class': 'com.github.gilesi.coverage.Agent',
+ 'Can-Retransform-Classes': 'true',
+ 'Can-Redefine-Classes': 'true',
+ 'Main-Class': 'com.github.gilesi.coverage.Agent',
+ 'Multi-Release': 'true'
+ }
+}
+
+application {
+ mainClass = 'com.github.gilesi.coverage.Agent'
+}
+
+description = 'Agent distribution.'
+
+shadowJar {
+ archiveBaseName.set('com.github.gilesi.coverage')
+ archiveClassifier.set('')
+ archiveVersion.set('')
+ mergeServiceFiles()
+
+ // Filtering shadow jar contents by file pattern.
+ exclude('LICENSE.txt')
+ exclude('META-INF/LICENSE')
+ exclude('META-INF/LICENSE.txt')
+ exclude('META-INF/NOTICE')
+ exclude('META-INF/NOTICE.txt')
+
+ // Relocating dependencies.
+ relocate('com.fasterxml', 'gilesi.com.fasterxml')
+ relocate('com.thoughtworks', 'gilesi.com.thoughtworks')
+ relocate('net', 'gilesi.net')
+ relocate('io', 'gilesi.io')
+ //relocate('org', 'gilesi.org') // causes issues with xtream if done
+}
+
+distributions {
+ shadow {
+ distributionBaseName = 'com.github.gilesi.coverage'
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea {
+ module {
+ downloadJavadoc = true
+ downloadSources = true
+ }
+}
\ No newline at end of file
diff --git a/TraceView/gradle/wrapper/gradle-wrapper.jar b/Coverage/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from TraceView/gradle/wrapper/gradle-wrapper.jar
rename to Coverage/gradle/wrapper/gradle-wrapper.jar
diff --git a/TraceView/gradle/wrapper/gradle-wrapper.properties b/Coverage/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from TraceView/gradle/wrapper/gradle-wrapper.properties
rename to Coverage/gradle/wrapper/gradle-wrapper.properties
diff --git a/TraceView/gradlew b/Coverage/gradlew
old mode 100644
new mode 100755
similarity index 100%
rename from TraceView/gradlew
rename to Coverage/gradlew
diff --git a/TraceView/gradlew.bat b/Coverage/gradlew.bat
similarity index 100%
rename from TraceView/gradlew.bat
rename to Coverage/gradlew.bat
diff --git a/Coverage/settings.gradle b/Coverage/settings.gradle
new file mode 100644
index 00000000..43813bd7
--- /dev/null
+++ b/Coverage/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'gilesi'
+
diff --git a/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java
new file mode 100644
index 00000000..10b23867
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java
@@ -0,0 +1,7 @@
+package com.github.gilesi.confgencoverage.models;
+
+public class Class {
+ public String ClassName;
+ public Method[] ClassMethods;
+ public String[] ClassDescriptors;
+}
diff --git a/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/CoverageParameters.java b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/CoverageParameters.java
new file mode 100644
index 00000000..5a43b75b
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/CoverageParameters.java
@@ -0,0 +1,6 @@
+package com.github.gilesi.confgencoverage.models;
+
+public class CoverageParameters {
+ public Class[] InstrumentedAPIClasses;
+ public String traceOutputLocation;
+}
diff --git a/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java
new file mode 100644
index 00000000..1e88a5f9
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java
@@ -0,0 +1,6 @@
+package com.github.gilesi.confgencoverage.models;
+
+public class Method {
+ public String MethodName;
+ public String[] MethodDescriptors;
+}
\ No newline at end of file
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/Agent.java b/Coverage/src/main/java/com/github/gilesi/coverage/Agent.java
new file mode 100644
index 00000000..1603c373
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/Agent.java
@@ -0,0 +1,149 @@
+package com.github.gilesi.coverage;
+
+import com.github.gilesi.confgencoverage.models.Class;
+import com.github.gilesi.confgencoverage.models.CoverageParameters;
+import com.github.gilesi.coverage.visitors.MethodInstrumentor;
+import net.bytebuddy.agent.ByteBuddyAgent;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.instrument.Instrumentation;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/*
+ Our main agent class
+ */
+public class Agent {
+ public static Path generatedLogsFolderPath;
+ public static Path generatedMarkersFolderPath;
+ public static Path generatedReachedListFolderPath;
+
+ /*
+ Our primary method to install the agent required to trace methods during execution
+ We take in as parameter an XML file with the list of methods to instrument and their class
+ */
+ private static void installAgent(String arg, Instrumentation inst) {
+ if (!RunMilestoneService.shouldRun()) {
+ return;
+ }
+
+ // Get the parameters
+ File configurationFile = new File(arg);
+ if (!configurationFile.exists()) {
+ System.err.printf("ERROR: the passed in configuration file (%s) does not exist or could not be found!%n", arg);
+ return;
+ }
+
+ CoverageParameters coverageParameters;
+
+ try {
+ coverageParameters = ConfigurationReader.parseCoverageparameters(configurationFile);
+ } catch (IOException ioException) {
+ System.err.printf("ERROR: Could not parse coverage configuration file (%s)!%n", arg);
+ System.err.println(ioException.getMessage());
+ ioException.printStackTrace();
+ return;
+ }
+
+ String generatedFolderLocation = coverageParameters.traceOutputLocation;
+ Path generatedFolderPath = Paths.get(generatedFolderLocation);
+ generatedLogsFolderPath = generatedFolderPath.resolve("logs");
+ generatedMarkersFolderPath = generatedFolderPath.resolve("markers");
+ generatedReachedListFolderPath = generatedFolderPath.resolve("reached");
+
+ if (!Files.isDirectory(generatedFolderPath)) {
+ try {
+ Files.createDirectory(generatedFolderPath);
+ } catch (IOException ioException) {
+ System.err.printf("ERROR: Could not create output directory (%s)!%n", arg);
+ System.err.println(ioException.getMessage());
+ ioException.printStackTrace();
+ return;
+ }
+ }
+
+ if (!Files.isDirectory(generatedLogsFolderPath)) {
+ try {
+ Files.createDirectory(generatedLogsFolderPath);
+ } catch (IOException ioException) {
+ System.err.printf("ERROR: Could not create logs output directory (%s)!%n", arg);
+ System.err.println(ioException.getMessage());
+ ioException.printStackTrace();
+ return;
+ }
+ }
+
+ if (!Files.isDirectory(generatedMarkersFolderPath)) {
+ try {
+ Files.createDirectory(generatedMarkersFolderPath);
+ } catch (IOException ioException) {
+ System.err.printf("ERROR: Could not create markers output directory (%s)!%n", arg);
+ System.err.println(ioException.getMessage());
+ ioException.printStackTrace();
+ return;
+ }
+ }
+
+ if (!Files.isDirectory(generatedReachedListFolderPath)) {
+ try {
+ Files.createDirectory(generatedReachedListFolderPath);
+ } catch (IOException ioException) {
+ System.err.printf("ERROR: Could not create reached list output directory (%s)!%n", arg);
+ System.err.println(ioException.getMessage());
+ ioException.printStackTrace();
+ return;
+ }
+ }
+
+ RunMilestoneService.writeMarker();
+
+ // Add a shutdown hook to save all logs at exit
+ Runtime.getRuntime().addShutdownHook(new Thread(Logger::SaveLogResults));
+ Runtime.getRuntime().addShutdownHook(new Thread(ReachedCollector::SaveReacedResults));
+
+ setupCoverage(inst, coverageParameters.InstrumentedAPIClasses);
+ }
+
+ private static void setupCoverage(Instrumentation inst, Class[] InstrumentedAPIClasses) {
+ // First, install the ByteBuddy Agent required to redefine classes during execution
+ // Sometimes this can fail, so try to catch it
+ try {
+ ByteBuddyAgent.install();
+ } catch (Throwable e) {
+ System.err.println("ERROR: Could not install byte buddy agent!");
+ System.err.println(e.getMessage());
+ e.printStackTrace();
+ return;
+ }
+
+ // Instrument every class
+ for (Class coverageParameter : InstrumentedAPIClasses) {
+ MethodInstrumentor.instrumentClassForTracingAgent(inst, coverageParameter);
+ }
+ }
+
+ /*
+ premain is invoked when an agent is started before the application.
+ Agents invoked using premain are specified with the -javaagent switch.
+ (https://stackoverflow.com/a/19789168)
+ */
+ public static void premain(String arg, Instrumentation inst) {
+ installAgent(arg, inst);
+ }
+
+ /*
+ agentmain is invoked when an agent is started after the application is already running.
+ Agents started with agentmain can be attached programmatically using the Sun tools API (for Sun/Oracle JVMs only
+ -- the method for introducing dynamic agents is implementation-dependent).
+ (https://stackoverflow.com/a/19789168)
+ */
+ public static void agentmain(String arg, Instrumentation inst) {
+ installAgent(arg, inst);
+ }
+
+ public static void main(String[] args) {
+ System.err.println("This program cannot be executed standalone.");
+ }
+}
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/ConfigurationReader.java b/Coverage/src/main/java/com/github/gilesi/coverage/ConfigurationReader.java
new file mode 100644
index 00000000..29c280a6
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/ConfigurationReader.java
@@ -0,0 +1,21 @@
+package com.github.gilesi.coverage;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.github.gilesi.confgencoverage.models.CoverageParameters;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ConfigurationReader {
+ public static final ObjectMapper objectMapper = new ObjectMapper()
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL)
+ .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+ .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES)
+ .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS);
+
+ public static CoverageParameters parseCoverageparameters(File file) throws IOException {
+ return objectMapper.readValue(file, CoverageParameters.class);
+ }
+}
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/Logger.java b/Coverage/src/main/java/com/github/gilesi/coverage/Logger.java
new file mode 100644
index 00000000..7fe41f0e
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/Logger.java
@@ -0,0 +1,58 @@
+package com.github.gilesi.coverage;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Logger {
+ private static final Map> LOGGER = new HashMap<>();
+ private static int LOGGERINSTANCE = 0;
+
+ public static void SaveLogResults() {
+ int mainPadding = 1;
+ String logFileName = String.format("%s%sgilesi.coverage_%d.log", Agent.generatedLogsFolderPath, File.separatorChar, mainPadding);
+
+ while (Files.exists(Paths.get(logFileName))) {
+ logFileName = String.format("%s%sgilesi.coverage_%d.log", Agent.generatedLogsFolderPath, File.separatorChar, ++mainPadding);
+ }
+
+ ArrayList logLines = new ArrayList<>();
+ for (int loggerInstance = 0; loggerInstance < LOGGERINSTANCE; loggerInstance++) {
+ ArrayList logs = LOGGER.get(loggerInstance);
+ if (logs != null) {
+ for (String log : logs) {
+ logLines.add(String.format("%d: %s", loggerInstance, log));
+ }
+ }
+ }
+
+ if (logLines.isEmpty()) {
+ return;
+ }
+
+ try {
+ Files.write(new File(logFileName).toPath(), logLines);
+ } catch (Throwable e) {
+ System.err.println("ERROR while saving logs");
+ e.printStackTrace();
+ }
+ }
+
+ public static int GetInstance() {
+ return LOGGERINSTANCE++;
+ }
+
+ public static void Log(int loggerInstance, String log) {
+ if (LOGGER.containsKey(loggerInstance)) {
+ ArrayList logs = LOGGER.get(loggerInstance);
+ logs.add(log);
+ } else {
+ ArrayList logs = new ArrayList<>();
+ logs.add(log);
+ LOGGER.put(loggerInstance, logs);
+ }
+ }
+}
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/ReachedCollector.java b/Coverage/src/main/java/com/github/gilesi/coverage/ReachedCollector.java
new file mode 100644
index 00000000..94cd1819
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/ReachedCollector.java
@@ -0,0 +1,39 @@
+package com.github.gilesi.coverage;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+
+public class ReachedCollector {
+ private static final ArrayList LOGGER = new ArrayList<>();
+
+ public static void SaveReacedResults() {
+ int mainPadding = 1;
+ String logFileName = String.format("%s%sgilesi.coverage_list_%d.log", Agent.generatedReachedListFolderPath, File.separatorChar, mainPadding);
+
+ while (Files.exists(Paths.get(logFileName))) {
+ logFileName = String.format("%s%sgilesi.coverage_list_%d.log", Agent.generatedReachedListFolderPath, File.separatorChar, ++mainPadding);
+ }
+
+ ArrayList logLines = new ArrayList<>();
+ for (String log : LOGGER) {
+ logLines.add(log);
+ }
+
+ if (logLines.isEmpty()) {
+ return;
+ }
+
+ try {
+ Files.write(new File(logFileName).toPath(), logLines);
+ } catch (Throwable e) {
+ System.err.println("ERROR while saving reached results");
+ e.printStackTrace();
+ }
+ }
+
+ public static void Log(String log) {
+ LOGGER.add(log);
+ }
+}
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/RunMilestoneService.java b/Coverage/src/main/java/com/github/gilesi/coverage/RunMilestoneService.java
new file mode 100644
index 00000000..bafac676
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/RunMilestoneService.java
@@ -0,0 +1,46 @@
+package com.github.gilesi.coverage;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+public class RunMilestoneService {
+ public static void writeMarker() {
+ RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
+ List arguments = runtimeMxBean.getInputArguments();
+
+ int mainPadding = 1;
+ String tracesFileName = String.format("%s%sgilesi.coverage_loaded_%d.marker", Agent.generatedMarkersFolderPath, File.separatorChar, mainPadding);
+
+ while (Files.exists(Paths.get(tracesFileName))) {
+ tracesFileName = String.format("%s%sgilesi.coverage_loaded_%d.marker", Agent.generatedMarkersFolderPath, File.separatorChar, ++mainPadding);
+ }
+
+ try {
+ Files.write(new File(tracesFileName).toPath(), arguments);
+ } catch (Throwable e) {
+ System.err.println("ERROR while saving marker");
+ e.printStackTrace();
+ }
+ }
+
+ public static boolean shouldRun() {
+ RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
+ List arguments = runtimeMxBean.getInputArguments();
+
+ for (String argument : arguments) {
+ if (argument.startsWith("-Dmaven.home=")) {
+ return false;
+ } else if (argument.startsWith("-Dorg.gradle.appname=")) {
+ return false;
+ } else if (argument.startsWith("-Dnet.bytebuddy.agent.attacher.dump=")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/CommonAdvisor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/CommonAdvisor.java
new file mode 100644
index 00000000..f09254ba
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/CommonAdvisor.java
@@ -0,0 +1,150 @@
+package com.github.gilesi.coverage.visitors;
+
+import com.github.gilesi.coverage.Logger;
+import com.github.gilesi.coverage.ReachedCollector;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class CommonAdvisor {
+ private static final String JUNIT_REFLECTION_UTILS_INVOKE_METHOD_FQN = "org.junit.platform.commons.util.ReflectionUtils.invokeMethod";
+ private static final String JUNIT_FRAMEWORK_METHOD_RUN_REFLECTIVE_CALL = "org.junit.runners.model.FrameworkMethod$1.runReflectiveCall";
+ private static final String UNKNOWN_TEST_METHOD_NAME = "UnknownTestPackage.UnknownTestClass.Unknown";
+ private static final ArrayList StackTraces = new ArrayList<>();
+
+ public static Object commonEnter() {
+ StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();
+ String[] stackTrace = Arrays.stream(stackTraceElements).skip(2).map(t -> String.format("%s.%s", t.getClassName(), t.getMethodName())).toArray(String[]::new);
+
+ if (isAcceptedTrace(stackTrace)) {
+ // Print reached api method here.
+
+ ReachedCollector.Log(StackTraces.get(StackTraces.size() - 1)[0]);
+
+ return "valid";
+ } else {
+ return null;
+ }
+ }
+
+ public static void commonExit(Object entryContext) {
+ if (entryContext == null) {
+ return;
+ }
+
+ StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();
+ String[] currentStackTrace = Arrays.stream(stackTraceElements).skip(2).map(t -> String.format("%s.%s", t.getClassName(), t.getMethodName())).toArray(String[]::new);
+
+ synchronized (StackTraces) {
+ // Remove existing stacktrace
+ for (int StoredPreviousStackTraceIndex = 0; StoredPreviousStackTraceIndex < StackTraces.size(); StoredPreviousStackTraceIndex++) {
+ String[] StoredPreviousStackTrace = StackTraces.get(StoredPreviousStackTraceIndex);
+
+ if (StoredPreviousStackTrace.length != currentStackTrace.length) {
+ continue;
+ }
+
+ boolean match = true;
+
+ for (int i = 0; i < StoredPreviousStackTrace.length; i++) {
+ String methodName = StoredPreviousStackTrace[i];
+ if (!methodName.equals(currentStackTrace[i])) {
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ StackTraces.remove(StoredPreviousStackTraceIndex);
+ break;
+ }
+ }
+ }
+ }
+
+ private static String getTestMethodName(String[] stackTrace) {
+ for (int i = 3; i < stackTrace.length; i++) {
+ String methodName = stackTrace[i];
+ if (methodName.equals(JUNIT_REFLECTION_UTILS_INVOKE_METHOD_FQN)) {
+ return stackTrace[i - 3];
+ }
+ }
+
+ for (int i = 5; i < stackTrace.length; i++) {
+ String methodName = stackTrace[i];
+ if (methodName.equals(JUNIT_FRAMEWORK_METHOD_RUN_REFLECTIVE_CALL)) {
+ return stackTrace[i - 5];
+ }
+ }
+
+ return UNKNOWN_TEST_METHOD_NAME;
+ }
+
+ private static String[] getCutOffStackTrace(String[] stackTrace) {
+ String testName = getTestMethodName(stackTrace);
+ ArrayList cutOffStackTrace = new ArrayList<>();
+ for (String stackTraceMethodName : stackTrace) {
+ if (stackTraceMethodName.startsWith("org.junit.") ||
+ stackTraceMethodName.startsWith("sun.") ||
+ stackTraceMethodName.startsWith("jdk.") ||
+ stackTraceMethodName.startsWith("java.")) {
+ continue;
+ }
+
+ cutOffStackTrace.add(stackTraceMethodName);
+
+ if (stackTraceMethodName.equals(testName)) {
+ break;
+ }
+ }
+
+ return cutOffStackTrace.stream().toArray(String[]::new);
+ }
+
+ private static boolean isAcceptedTrace(String[] stackTrace) {
+ synchronized (StackTraces) {
+ int loggerInstance = Logger.GetInstance();
+
+ stackTrace = getCutOffStackTrace(stackTrace);
+
+ Logger.Log(loggerInstance, "++CommonAdvisor::isAcceptedTrace");
+
+ Logger.Log(loggerInstance, " Incoming StackTrace:");
+ for (String methodName : stackTrace) {
+ Logger.Log(loggerInstance, String.format(" %s", methodName));
+ }
+
+ for (String[] previousStackTrace : StackTraces) {
+ previousStackTrace = getCutOffStackTrace(previousStackTrace);
+
+ // An existing stacktrace can only be smaller than us if they overlap
+ if (previousStackTrace.length >= stackTrace.length) {
+ continue;
+ }
+
+ boolean matchesExistingStackTrace = true;
+ for (int i = 0; i < previousStackTrace.length; i++) {
+ String oldMethod = previousStackTrace[previousStackTrace.length - 1 - i];
+ String newMethod = stackTrace[stackTrace.length - 1 -i];
+
+ if (!oldMethod.equals(newMethod)) {
+ matchesExistingStackTrace = false;
+ break;
+ }
+ }
+
+ if (matchesExistingStackTrace) {
+ Logger.Log(loggerInstance, " Rejected Trace");
+ Logger.Log(loggerInstance, "--CommonAdvisor::isAcceptedTrace");
+
+ return false;
+ }
+ }
+
+ Logger.Log(loggerInstance, " Accepted Trace");
+ Logger.Log(loggerInstance, "--CommonAdvisor::isAcceptedTrace");
+ StackTraces.add(stackTrace);
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/ConstructorAdvisor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/ConstructorAdvisor.java
new file mode 100644
index 00000000..5c2be157
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/ConstructorAdvisor.java
@@ -0,0 +1,26 @@
+package com.github.gilesi.coverage.visitors;
+
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.reflect.Executable;
+
+public class ConstructorAdvisor {
+ @Advice.OnMethodEnter(inline = false)
+ public static Object enter(
+ @Advice.Origin Executable origin,
+ @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments,
+ @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance) {
+ return CommonAdvisor.commonEnter();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(
+ @Advice.Origin Executable origin,
+ @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returned,
+ @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance,
+ @Advice.Enter(typing = Assigner.Typing.DYNAMIC) String entryTrace) {
+ CommonAdvisor.commonExit(entryTrace);
+ }
+}
\ No newline at end of file
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/LoggingListener.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/LoggingListener.java
new file mode 100644
index 00000000..778272bb
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/LoggingListener.java
@@ -0,0 +1,50 @@
+package com.github.gilesi.coverage.visitors;
+
+import com.github.gilesi.coverage.Logger;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.utility.JavaModule;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class LoggingListener implements AgentBuilder.Listener {
+ private int loggerInstance = Logger.GetInstance();
+
+ @Override
+ public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module,
+ boolean loaded) {
+ }
+
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader,
+ JavaModule module, boolean loaded, DynamicType dynamicType) {
+
+ }
+
+ @Override
+ public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader,
+ JavaModule module, boolean loaded) {
+ }
+
+ @Override
+ public void onError(String typeName, ClassLoader classLoader, JavaModule module,
+ boolean loaded, Throwable throwable) {
+ Logger.Log(loggerInstance, String.format("BYTEBUDDY INSTRUMENTATION ERROR:"));
+ Logger.Log(loggerInstance, String.format("Type Name: %s", typeName));
+ Logger.Log(loggerInstance, String.format("Loaded: %s", loaded));
+ Logger.Log(loggerInstance, String.format("Exception: %s", throwable));
+
+ StringWriter writer = new StringWriter();
+ throwable.printStackTrace(new PrintWriter(writer));
+ String stackTrace = writer.toString();
+
+ Logger.Log(loggerInstance, String.format("Stack Trace: %s", stackTrace));
+ }
+
+ @Override
+ public void onComplete(String typeName, ClassLoader classLoader, JavaModule module,
+ boolean loaded) {
+ }
+}
\ No newline at end of file
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodAdvisor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodAdvisor.java
new file mode 100644
index 00000000..5bcc1dd2
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodAdvisor.java
@@ -0,0 +1,27 @@
+package com.github.gilesi.coverage.visitors;
+
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.reflect.Executable;
+
+public class MethodAdvisor {
+ @Advice.OnMethodEnter(inline = false)
+ public static Object enter(
+ @Advice.Origin Executable origin,
+ @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments,
+ @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance) {
+ return CommonAdvisor.commonEnter();
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, inline = false)
+ public static void exit(
+ @Advice.Origin Executable origin,
+ @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returned,
+ @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance,
+ @Advice.Thrown Throwable thrown,
+ @Advice.Enter(typing = Assigner.Typing.DYNAMIC) String entryTrace) {
+ CommonAdvisor.commonExit(entryTrace);
+ }
+}
\ No newline at end of file
diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodInstrumentor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodInstrumentor.java
new file mode 100644
index 00000000..f501c9dd
--- /dev/null
+++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodInstrumentor.java
@@ -0,0 +1,82 @@
+package com.github.gilesi.coverage.visitors;
+
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
+
+import java.lang.instrument.Instrumentation;
+
+import com.github.gilesi.confgencoverage.models.Class;
+import com.github.gilesi.confgencoverage.models.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class MethodInstrumentor {
+ // commented out due to hooking issues
+ // yet to investigate
+ /*private static ElementMatcher.Junction getConstructorElementMatcherFromClassParameter(Class classParameter) {
+ ElementMatcher.Junction descriptorMatcher = null;
+ for (String descriptorParameter : classParameter.ClassDescriptors) {
+ if (descriptorMatcher == null) {
+ descriptorMatcher = hasDescriptor(descriptorParameter);
+ } else {
+ descriptorMatcher = descriptorMatcher.or(hasDescriptor(descriptorParameter));
+ }
+ }
+ return descriptorMatcher;
+ }*/
+
+ private static ElementMatcher.Junction getMethodElementMatcherFromClassParameter(Class classParameter) {
+ ElementMatcher.Junction classMatcher = null;
+ for (Method methodParameter : classParameter.ClassMethods) {
+ ElementMatcher.Junction methodMatcher = named(methodParameter.MethodName);
+ // commented out due to hooking issues
+ // yet to investigate
+ /*ElementMatcher.Junction descriptorMatcher = null;
+ for (String descriptorParameter : methodParameter.MethodDescriptors) {
+ if (descriptorMatcher == null) {
+ descriptorMatcher = hasDescriptor(descriptorParameter);
+ } else {
+ descriptorMatcher = descriptorMatcher.or(hasDescriptor(descriptorParameter));
+ }
+ }
+
+ if (descriptorMatcher != null) {
+ methodMatcher = methodMatcher.and(descriptorMatcher);
+ }*/
+
+ if (classMatcher == null) {
+ classMatcher = methodMatcher;
+ } else {
+ classMatcher = classMatcher.or(methodMatcher);
+ }
+ }
+ return classMatcher;
+ }
+
+ public static void instrumentClassForTracingAgent(Instrumentation inst, Class classParameter) {
+ ElementMatcher.Junction methodMatcher = getMethodElementMatcherFromClassParameter(classParameter);
+ //ElementMatcher.Junction constructorMatcher = getConstructorElementMatcherFromClassParameter(classParameter);
+
+ new AgentBuilder.Default()
+ .with(new LoggingListener())
+ .type(named(classParameter.ClassName))
+ .transform((builder,
+ typeDescription,
+ classLoader,
+ javaModule,
+ protectionDomain) ->
+ builder
+ .visit(
+ Advice.to(MethodAdvisor.class).on(ElementMatchers.not(ElementMatchers.isTypeInitializer().or(ElementMatchers.isConstructor())).and(methodMatcher))
+ )
+ .visit(
+ Advice.to(ConstructorAdvisor.class).on(ElementMatchers.not(ElementMatchers.isTypeInitializer()).and(ElementMatchers.isConstructor())/*.and(constructorMatcher)*/)
+ )
+ )
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE).installOn(inst);
+ }
+}
\ No newline at end of file
diff --git a/TraceView/.gitignore b/Illico/.gitignore
similarity index 100%
rename from TraceView/.gitignore
rename to Illico/.gitignore
diff --git a/Illico/build.gradle b/Illico/build.gradle
new file mode 100644
index 00000000..c9155048
--- /dev/null
+++ b/Illico/build.gradle
@@ -0,0 +1,81 @@
+plugins {
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '8.1.1'
+}
+
+group 'com.github.gilesi.illico'
+version '1.0-SNAPSHOT'
+
+
+compileJava {
+ options.encoding = 'UTF-8'
+}
+
+tasks.withType(JavaCompile).configureEach {
+ options.encoding = 'UTF-8'
+}
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+ maven {
+ url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies'
+ }
+}
+
+dependencies {
+ implementation 'com.fasterxml.jackson.core:jackson-core:2.17.2'
+ implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2'
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2'
+ implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2'
+ implementation 'com.fasterxml.jackson:jackson-base:2.17.2'
+ implementation 'org.apache.maven:maven-core:3.9.9'
+ implementation 'org.apache.maven.shared:maven-invoker:3.3.0'
+ testImplementation platform('org.junit:junit-bom:5.11.0')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ implementation 'org.apache.logging.log4j:log4j-core:2.24.0'
+ implementation 'org.apache.logging.log4j:log4j-api:2.24.0'
+}
+
+jar {
+ manifest {
+ attributes 'Main-Class': 'com.github.gilesi.illico.Main',
+ 'Multi-Release': 'true'
+ }
+}
+
+application {
+ mainClass = 'com.github.gilesi.illico.Main'
+}
+
+description = 'Main distribution.'
+
+shadowJar {
+ archiveBaseName.set('com.github.gilesi.illico')
+ archiveClassifier.set('')
+ archiveVersion.set('')
+ mergeServiceFiles()
+}
+
+distributions {
+ shadow {
+ distributionBaseName = 'com.github.gilesi.illico'
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea {
+ module {
+ downloadJavadoc = true
+ downloadSources = true
+ }
+}
+
+run {
+ jvmArgs = [
+ "-XX:InitialHeapSize=2G",
+ "-XX:MaxHeapSize=32G"
+ ]
+}
\ No newline at end of file
diff --git a/Illico/gradle/wrapper/gradle-wrapper.jar b/Illico/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..249e5832
Binary files /dev/null and b/Illico/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Illico/gradle/wrapper/gradle-wrapper.properties b/Illico/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..5c07b32a
--- /dev/null
+++ b/Illico/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon May 27 14:15:06 CEST 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/Illico/gradlew b/Illico/gradlew
new file mode 100755
index 00000000..1b6c7873
--- /dev/null
+++ b/Illico/gradlew
@@ -0,0 +1,234 @@
+#!/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.
+#
+
+##############################################################################
+#
+# 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/master/subprojects/plugins/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
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# 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"'
+
+# 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
+ which java >/dev/null 2>&1 || 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
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ 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
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# 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/Illico/gradlew.bat b/Illico/gradlew.bat
new file mode 100644
index 00000000..107acd32
--- /dev/null
+++ b/Illico/gradlew.bat
@@ -0,0 +1,89 @@
+@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
+
+@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=.
+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%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+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%"=="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!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Illico/settings.gradle b/Illico/settings.gradle
new file mode 100644
index 00000000..19c50608
--- /dev/null
+++ b/Illico/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'Illico'
+
diff --git a/Illico/src/main/java/com/github/gilesi/illico/Constants.java b/Illico/src/main/java/com/github/gilesi/illico/Constants.java
new file mode 100644
index 00000000..8e770a81
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/Constants.java
@@ -0,0 +1,41 @@
+package com.github.gilesi.illico;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+public class Constants {
+ // These paths should already exist on the end user machine for now and never change
+ public static final String CONF_GEN_LOCATION = "ConfGen/build/libs/com.github.gilesi.confgen.jar";
+ public static final String TEST_GEN_LOCATION = "TestGenerator/build/libs/com.github.gilesi.testgenerator.jar";
+ public static final String TRACE_DIFF_LOCATION = "TraceDiff/build/libs/com.github.gilesi.tracediff.jar";
+ public static final String INSTRUMENTATION_LOCATION = "Instrumentation/build/libs/com.github.gilesi.instrumentation.jar";
+
+ public static final String JAVA_21_BINARY_LOCATION = "/usr/lib/jvm/java-21-openjdk-amd64";
+
+ // These paths are dynamically created by the tool but must stay consistent within methods
+ public static final String targetAgentName = "com.github.gilesi.instrumentation.jar";
+ public static final String targetConfigurationName = "TestWorkflowConfiguration.json";
+
+ public static final Map JAVA_VERSIONS = new Hashtable<>() {
+ {
+ put("1.5", "/usr/lib/jvm/java-8-openjdk-amd64");
+ put("1.6", "/usr/lib/jvm/java-8-openjdk-amd64");
+ put("1.7", "/usr/lib/jvm/java-8-openjdk-amd64");
+ put("1.8", "/usr/lib/jvm/java-8-openjdk-amd64");
+ put("11", "/usr/lib/jvm/java-11-openjdk-amd64");
+ put("17", "/usr/lib/jvm/java-17-openjdk-amd64");
+ put("21", JAVA_21_BINARY_LOCATION);
+ }
+ };
+
+ public static final String GREEN_TEST = "Green";
+ public static final String RED_TEST = "Red";
+
+ public static final String SUREFIRE_VERSION = "2.8";
+ public static final String SUREFIRE_GROUPID = "org.apache.maven.plugins";
+ public static final String SUREFIRE_ARTIFACTID = "maven-surefire-plugin";
+
+ public static boolean SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING = false;
+
+ public static final String M2_REPOSITORY = "/home/gus/.m2/repository";
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/FileUtils.java b/Illico/src/main/java/com/github/gilesi/illico/FileUtils.java
new file mode 100644
index 00000000..dab7ee8e
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/FileUtils.java
@@ -0,0 +1,130 @@
+package com.github.gilesi.illico;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+public class FileUtils {
+ public static void deleteDirectory(File fi) {
+ for (File file : Objects.requireNonNull(fi.listFiles())) {
+ if (file.isDirectory()) {
+ deleteDirectory(file);
+ }
+ file.delete();
+ }
+ fi.delete();
+ }
+
+ public static void deleteDirectoryIfExists(String path) {
+ Path pa = Path.of(path);
+ if (Files.exists(pa)) {
+ deleteDirectory(new File(path));
+ }
+ }
+
+ public static ArrayList enumerateDirectory(File fi, String wildcard, boolean recursive) {
+ ArrayList files = new ArrayList<>();
+ Pattern pattern = Pattern.compile(wildcard);
+ for (File file : Objects.requireNonNull(fi.listFiles())) {
+ if (file.isDirectory() && recursive) {
+ files.addAll(enumerateDirectory(file, wildcard, true));
+ } else {
+ if (pattern.matcher(file.getName()).matches()) {
+ files.add(file.getPath());
+ }
+ }
+ }
+ return files;
+ }
+
+ public static ArrayList enumerateFiles(String path, String wildcard, boolean recursive) {
+ Path pa = Path.of(path);
+ if (Files.exists(pa)) {
+ return enumerateDirectory(new File(path), wildcard, recursive);
+ }
+ return new ArrayList<>();
+ }
+
+ public static void ensureDirectoryExistsAndIsEmpty(String path) throws IOException {
+ Path pa = Path.of(path);
+ deleteDirectoryIfExists(path);
+ Files.createDirectory(pa);
+ }
+
+ public static void backupFile(String fileLocation) throws IOException {
+ Path filePath = Path.of(fileLocation);
+ Path backupFilePath = Path.of("%s.orig".formatted(fileLocation));
+
+ if (!Files.exists(filePath)) {
+ return;
+ }
+
+ if (Files.exists(backupFilePath)) {
+ Files.delete(backupFilePath);
+ }
+
+ Files.copy(filePath, backupFilePath);
+ }
+
+ public static void restoreFile(String fileLocation) throws IOException {
+ Path filePath = Path.of(fileLocation);
+ Path backupFilePath = Path.of("%s.orig".formatted(fileLocation));
+
+ if (!Files.exists(backupFilePath)) {
+ return;
+ }
+
+ if (Files.exists(filePath)) {
+ Files.delete(filePath);
+ }
+
+ Files.move(backupFilePath, filePath);
+ }
+
+ public static void copyFolder(File source, File destination) {
+ if (source.isDirectory()) {
+ if (!destination.exists()) {
+ destination.mkdirs();
+ }
+
+ String[] files = source.list();
+
+ for (String file : files) {
+ File srcFile = new File(source, file);
+ File destFile = new File(destination, file);
+
+ copyFolder(srcFile, destFile);
+ }
+ } else {
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ in = new FileInputStream(source);
+ out = new FileOutputStream(destination);
+
+ byte[] buffer = new byte[1024];
+
+ int length;
+ while ((length = in.read(buffer)) > 0) {
+ out.write(buffer, 0, length);
+ }
+ } catch (Exception e) {
+ try {
+ in.close();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ try {
+ out.close();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/Main.java b/Illico/src/main/java/com/github/gilesi/illico/Main.java
new file mode 100644
index 00000000..bad0bd8a
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/Main.java
@@ -0,0 +1,194 @@
+package com.github.gilesi.illico;
+
+import org.apache.maven.shared.invoker.MavenInvocationException;
+
+import com.github.gilesi.illico.runners.maven.Log4JLogger;
+import com.github.gilesi.illico.runners.maven.ProjectRunner;
+import com.github.gilesi.illico.runners.maven.TestAssertedLogger;
+import com.github.gilesi.illico.tools.ConfGen;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+
+public class Main {
+ public static final Logger logger = LogManager.getLogger("illico");
+ public static final Logger loggerMaven = LogManager.getLogger("maven");
+ public static final Logger loggerConfGen = LogManager.getLogger("confgen");
+ public static final Logger loggerTraceDiff = LogManager.getLogger("tracediff");
+ public static final Logger loggerTraceGen = LogManager.getLogger("tracegen");
+ public static final Logger loggerAgent = LogManager.getLogger("agent");
+
+ public static void main(String[] args) throws MavenInvocationException, IOException, InterruptedException {
+ if (System.getenv("MAVEN_HOME") == null || !Files.exists(Path.of(System.getenv("MAVEN_HOME"))) || Files.isRegularFile(Path.of(System.getenv("MAVEN_HOME")))) {
+ logger.info("You must specify the MAVEN_HOME environment variable pointing to a valid Maven directory");
+ return;
+ }
+
+ if (args.length < 4) {
+ logger.info("Usage: