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/Illico/.gitignore
similarity index 100%
rename from Samples/sampleclient/.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: ");
+ return;
+ }
+
+ String clientLocation = args[0];
+ String libraryIdentifier = args[1];
+ String outputFolder = args[2];
+ String GilesiRepositoryLocation = args[3];
+ String optionalCustomTestCommand = null;
+
+ if (args.length > 4) {
+ optionalCustomTestCommand = args[4];
+ }
+
+ ProjectRunner.runMavenGoalOnRepository(
+ clientLocation,
+ "dependency:sources",
+ null,
+ "1.8",
+ new Log4JLogger(),
+ null
+ );
+
+ Path outputPath = Path.of(outputFolder).toAbsolutePath();
+
+ Path libraryPath = outputPath.resolve("library-sources");
+
+ copyLibrarySourcesForDataset(libraryIdentifier, libraryPath, Path.of(Constants.M2_REPOSITORY));
+
+ String additionalJavaToolParameters = installInstrumentationAgentOntoProject(Path.of(GilesiRepositoryLocation), libraryPath, Path.of(clientLocation), outputPath.resolve("instrumentation"));
+
+ String testCmd = "test -fn -Drat.ignoreErrors=true -DtrimStackTrace=false -DfailIfNoTests=false";
+
+ if (optionalCustomTestCommand != null &&
+ !optionalCustomTestCommand.isEmpty() &&
+ !optionalCustomTestCommand.equals("N/A")) {
+ testCmd = optionalCustomTestCommand;
+ }
+
+ if (testCmd.startsWith("mvn ")) {
+ testCmd = testCmd.substring(4);
+ }
+
+ TestAssertedLogger testAssertedLogger = new TestAssertedLogger(
+ new Log4JLogger(
+ logger,
+ org.apache.maven.shared.invoker.InvokerLogger.INFO,
+ "%s-%s".formatted(clientLocation, clientLocation)
+ )
+ );
+
+ ProjectRunner.runMavenGoalOnRepository(clientLocation,
+ testCmd,
+ null,
+ "1.8",
+ testAssertedLogger,
+ additionalJavaToolParameters);
+
+ Main.logger.info("Client: %s - TEST(S) FAILED: %s".formatted(clientLocation,
+ testAssertedLogger.HasTestFailed()));
+ }
+
+
+ private static String installInstrumentationAgentOntoProject(
+ Path GilesiRepositoryLocationPath,
+ Path libraryLocationPath,
+ Path clientLocationPath,
+ Path generatedFolderPath
+ ) throws IOException, InterruptedException {
+
+ FileUtils.deleteDirectoryIfExists(generatedFolderPath.toString());
+
+ Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName);
+
+ // Run ConfGen on Library first
+ // TODO: Check how meta projects get handled here exactly...
+ ConfGen.runConfGen(GilesiRepositoryLocationPath.toString(),
+ libraryConfig.toString(),
+ generatedFolderPath.toString(),
+ libraryLocationPath.toString());
+
+ Path InstrumentationAgentEffectiveLocation = GilesiRepositoryLocationPath.resolve(Constants.INSTRUMENTATION_LOCATION);
+
+ // Modify Client Build System to use the instrumentation agent
+ ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocationPath.toString(),
+ "pom.xml",
+ true);
+ ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocationPath.toString(),
+ "build.gradle",
+ true);
+
+ ArrayList allProjectFiles = new ArrayList<>();
+ allProjectFiles.addAll(clientProjectPomFiles);
+ allProjectFiles.addAll(clientProjectGradleBuildFiles);
+
+ Orchestrator.copyInstrumentationToolAndConfiguration(allProjectFiles,
+ InstrumentationAgentEffectiveLocation,
+ libraryConfig);
+
+ String javaToolOptions = "-javaagent:%s=%s".formatted(InstrumentationAgentEffectiveLocation,
+ libraryConfig.toString());
+
+ Orchestrator.orchestrateGradleProjects(clientProjectGradleBuildFiles);
+
+ return javaToolOptions;
+ }
+
+ private static void copyLibrarySourcesForDataset(String libraryIdentifier, Path librarySourceOutputPath, Path m2RepositoryPath) throws IOException, MavenInvocationException {
+ String[] groupId = libraryIdentifier.split(":")[0].split("\\.");
+ String artifactId = libraryIdentifier.split(":")[1];
+ String libraryVersion = libraryIdentifier.split(":")[2];
+
+ Path fileSystemRepoPath = m2RepositoryPath;
+
+ for (String pathComponent : groupId) {
+ fileSystemRepoPath = fileSystemRepoPath.resolve(pathComponent);
+ }
+
+ fileSystemRepoPath = fileSystemRepoPath.resolve(artifactId).resolve(libraryVersion);
+
+ ArrayList srcCandidates = FileUtils.enumerateFiles(fileSystemRepoPath.toString(), ".*sources\\.jar", false);
+
+ if (srcCandidates.size() == 0) {
+ Main.logger.info("%s Missing sources for lib, aborting!".formatted(libraryIdentifier));
+ return;
+ }
+
+ FileUtils.deleteDirectoryIfExists(librarySourceOutputPath.toString());
+ Files.createDirectories(librarySourceOutputPath);
+
+ for (String cand : srcCandidates) {
+ System.out.println("Extracting " + cand);
+ System.out.println("Extracting " + librarySourceOutputPath);
+ extractJar(cand, librarySourceOutputPath.toString());
+ }
+ }
+
+ private static void extractJar(String jar, String destdir) throws java.io.IOException {
+ java.util.jar.JarFile jarFile = new java.util.jar.JarFile(new java.io.File(jar));
+ java.util.Enumeration enu = jarFile.entries();
+ while (enu.hasMoreElements()) {
+ java.util.jar.JarEntry je = enu.nextElement();
+
+ java.io.File fl = new java.io.File(destdir, je.getName());
+ if (!fl.exists()) {
+ fl.getParentFile().mkdirs();
+ fl = new java.io.File(destdir, je.getName());
+ }
+ if (je.isDirectory()) {
+ continue;
+ }
+ java.io.InputStream is = jarFile.getInputStream(je);
+ java.io.FileOutputStream fo = new java.io.FileOutputStream(fl);
+ while (is.available() > 0) {
+ fo.write(is.read());
+ }
+ fo.close();
+ is.close();
+ }
+ jarFile.close();
+ }
+}
\ No newline at end of file
diff --git a/Illico/src/main/java/com/github/gilesi/illico/Orchestrator.java b/Illico/src/main/java/com/github/gilesi/illico/Orchestrator.java
new file mode 100644
index 00000000..671f82d2
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/Orchestrator.java
@@ -0,0 +1,267 @@
+package com.github.gilesi.illico;
+
+import com.github.gilesi.illico.buildsystem.configuration.maven.PomHelper;
+import com.github.gilesi.illico.runners.maven.Log4JLogger;
+import com.github.gilesi.illico.runners.maven.ProjectRunner;
+import com.github.gilesi.illico.tools.ConfGen;
+import com.github.gilesi.illico.tools.TestGen;
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+
+public class Orchestrator {
+ // TODO: More robust logging here using JUnit perhaps...
+ // TODO: Simplify required arg to run here
+ // TODO: Ideally we would want to deduce the version from the provided source but not always doable
+ // TODO: Could we also fetch source jars ourselves somehow or source loc?
+ // TODO: Maybe the dependency name is also doable to extract here, need to see how...
+ // TODO: Fix cross plat!
+ // TODO: picocli?
+
+ public static void handleProject(String clientLocation, String libraryLocation, String GilesiRepositoryLocation, String dependencyName, String dependencyVersion, String outputTestProject) throws XmlPullParserException, IOException, MavenInvocationException, InterruptedException {
+ FileUtils.deleteDirectoryIfExists(outputTestProject);
+ Path outputTestProjectPath = Path.of(outputTestProject);
+
+ Path libraryLocationPath = Path.of(libraryLocation);
+ Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName);
+
+ // Create the results directory
+ Files.createDirectories(outputTestProjectPath);
+
+ // Run ConfGen on Library first
+ // TODO: Check how meta projects get handled here exactly...
+ ConfGen.runConfGen(GilesiRepositoryLocation, libraryConfig.toString(), outputTestProject, libraryLocation);
+
+ Path InstrumentationAgentEffectiveLocation = Path.of("%s%s".formatted(GilesiRepositoryLocation, Constants.INSTRUMENTATION_LOCATION));
+
+ // Modify Client Build System to use the instrumentation agent
+ ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocation, "pom.xml", true);
+ ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocation, "build.gradle", true);
+
+ ArrayList allProjectFiles = new ArrayList<>();
+ allProjectFiles.addAll(clientProjectPomFiles);
+ allProjectFiles.addAll(clientProjectGradleBuildFiles);
+
+ copyInstrumentationToolAndConfiguration(allProjectFiles, InstrumentationAgentEffectiveLocation, libraryConfig);
+
+ orchestrateMavenProjects(clientProjectPomFiles);
+ orchestrateGradleProjects(clientProjectGradleBuildFiles);
+
+ // Run the client tests
+ executeMavenProjectTests(clientProjectPomFiles, clientLocation);
+ executeGradleProjectTests(clientProjectGradleBuildFiles, clientLocation);
+
+ TestGen.runTestGen(GilesiRepositoryLocation, outputTestProject, outputTestProjectPath.resolve("src").resolve("test").resolve("java").toString(), libraryConfig.toString());
+
+ lastMinuteCleanup(allProjectFiles);
+
+ // Generate Gradle test project here in dir outputTestProject, with dep dependencyName of version dependencyVersion
+
+ String buildGradleProjectFile = """
+ plugins {
+ id 'java'
+ }
+
+ group = 'testProject'
+ version = '1.0-SNAPSHOT'
+
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ }
+
+ dependencies {
+ testImplementation '%s:%s'
+ testImplementation 'com.thoughtworks.xstream:xstream:1.4.20'
+ testImplementation platform('org.junit:junit-bom:5.10.0')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ }
+
+ test {
+ useJUnitPlatform()
+ }""".formatted(dependencyName, dependencyVersion);
+
+ Files.writeString(outputTestProjectPath.resolve("build.gradle"), buildGradleProjectFile);
+ Files.copy(Path.of(GilesiRepositoryLocation).resolve("Illico").resolve("gradlew"), outputTestProjectPath.resolve("gradlew"));
+ Files.copy(Path.of(GilesiRepositoryLocation).resolve("Illico").resolve("gradlew.bat"), outputTestProjectPath.resolve("gradlew.bat"));
+
+ // Test test project (3 times to be sure)
+
+ // Update test project dependency version at version dependencyNewVersion
+
+ // Test test project with dependency at version dependencyNewVersion (3 times to be sure)
+
+ // Collect results of the execution
+
+ // Write report
+ }
+
+ public static void copyInstrumentationToolAndConfiguration(ArrayList allProjectFiles, Path InstrumentationAgentEffectiveLocation, Path libraryConfig) throws IOException {
+ for (String clientBuildFile : allProjectFiles) {
+ Path clientBuildFilePath = Path.of(clientBuildFile);
+ Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath();
+ Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName);
+ Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName);
+
+ Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile));
+ if (Files.exists(clientBuildFileBackup)) {
+ Files.deleteIfExists(clientBuildFilePath);
+ Files.move(clientBuildFileBackup, clientBuildFilePath);
+ }
+
+ Files.deleteIfExists(agentDest);
+ Files.deleteIfExists(workflowDest);
+
+ //Files.copy(InstrumentationAgentEffectiveLocation, agentDest);
+ //Files.copy(libraryConfig, workflowDest);
+ }
+ }
+
+ public static void orchestrateMavenProjects(ArrayList clientProjectPomFiles) throws IOException, XmlPullParserException {
+ for (String clientBuildFile : clientProjectPomFiles) {
+ Path clientBuildFilePath = Path.of(clientBuildFile);
+ Files.copy(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile)));
+
+ String clientSurefireArgLine = "-Dnet.bytebuddy.experimental=true -javaagent:%s=%s".formatted(Constants.targetAgentName, Constants.targetConfigurationName);
+ PomHelper.addSureFire(clientBuildFile, clientSurefireArgLine);
+ }
+ }
+
+ public static void orchestrateGradleProjects(ArrayList clientProjectGradleBuildFiles) throws IOException {
+ for (String clientBuildFile : clientProjectGradleBuildFiles) {
+ String clientTestingBlock = """
+ testing {
+ \tsuites {
+ \t\ttest {
+ \t\t\tuseJUnitJupiter()
+ \t\t\ttargets {
+ \t\t\t\tall {
+ \t\t\t\t\ttestTask.configure {
+ \t\t\t\t\t\tsystemProperty 'net.bytebuddy.experimental', 'true'
+ \t\t\t\t\t\tjvmArgs = ['-XX:+EnableDynamicAgentLoading', '-javaagent:%s=%s']
+ \t\t\t\t\t}
+ \t\t\t\t}
+ \t\t\t}
+ \t\t}
+ \t}
+ }""".formatted(Constants.targetAgentName, Constants.targetConfigurationName);
+
+ Path clientBuildFilePath = Path.of(clientBuildFile);
+
+ String buildFileContent = Files.readString(clientBuildFilePath);
+ if (!buildFileContent.contains(clientTestingBlock)) {
+ buildFileContent += "\n" + clientTestingBlock;
+
+ Files.move(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile)));
+ Files.writeString(clientBuildFilePath, buildFileContent);
+ }
+ }
+ }
+
+ public static void executeMavenProjectTests(ArrayList clientProjectPomFiles, String clientLocation) throws MavenInvocationException {
+ // This is the maven path
+ if (!clientProjectPomFiles.isEmpty()) {
+ if (clientProjectPomFiles.stream().anyMatch(t -> t.replace("%s%c".formatted(clientLocation, File.separatorChar), "").replace(clientLocation, "").equals("pom.xml"))) {
+ ProjectRunner.runMavenGoalOnRepository(clientLocation, "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null);
+ } else {
+ for (String clientPomFile : clientProjectPomFiles) {
+ Path clientPomFilePath = Path.of(clientPomFile);
+ Path clientProjectLocationPath = clientPomFilePath.getParent().toAbsolutePath();
+
+ ProjectRunner.runMavenGoalOnRepository(clientProjectLocationPath.toString(), "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null);
+ }
+ }
+ }
+ }
+
+ public static void executeGradleProjectTests(ArrayList clientProjectGradleBuildFiles, String clientLocation) throws IOException, InterruptedException {
+ // This is the gradle path
+ if (!clientProjectGradleBuildFiles.isEmpty()) {
+ if (clientProjectGradleBuildFiles.stream().anyMatch
+ (
+ t ->
+ t
+ .replace(
+ "%s%c"
+ .formatted(
+ clientLocation,
+ File.separatorChar
+ ),
+ ""
+ )
+ .replace(
+ clientLocation,
+ ""
+ )
+ .equals("build.gradle")
+ )) {
+ ProcessUtils.runExternalCommand(new String[]{
+ "/usr/bin/env",
+ "bash",
+ Path.of(clientLocation).resolve("gradlew").toString(),
+ "test",
+ "--warning-mode",
+ "all"
+ }, new File(clientLocation), Main.logger);
+
+ // TODO: Check for underlying platform
+ /*ProcessUtils.runExternalCommand(new String[]{
+ "cmd.exe",
+ "-C",
+ Path.of(clientLocation).resolve("gradlew").toString(),
+ "test",
+ "--warning-mode",
+ "all"
+ }, new File(clientLocation), Main.logger);*/
+ } else {
+ for (String clientGradleBuildFile : clientProjectGradleBuildFiles) {
+ Path clientGradleBuildFilePath = Path.of(clientGradleBuildFile);
+ Path clientProjectLocationPath = clientGradleBuildFilePath.getParent().toAbsolutePath();
+
+ ProcessUtils.runExternalCommand(new String[]{
+ "/usr/bin/env",
+ "bash",
+ clientProjectLocationPath.resolve("gradlew").toString(),
+ "test",
+ "--warning-mode",
+ "all"
+ }, new File(clientProjectLocationPath.toString()), Main.logger);
+
+ // TODO: Check for underlying platform
+ /*ProcessUtils.runExternalCommand(new String[]{
+ "cmd.exe",
+ "-C",
+ clientProjectLocationPath.resolve("gradlew.bat").toString(),
+ "test",
+ "--warning-mode",
+ "all"
+ }, new File(clientProjectLocationPath.toString()), Main.logger);*/
+ }
+ }
+ }
+ }
+
+ public static void lastMinuteCleanup(ArrayList allProjectFiles) throws IOException {
+ // Last minute cleanup
+ for (String clientBuildFile : allProjectFiles) {
+ Path clientBuildFilePath = Path.of(clientBuildFile);
+ Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath();
+ Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName);
+ Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName);
+
+ Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile));
+ if (Files.exists(clientBuildFileBackup)) {
+ Files.deleteIfExists(clientBuildFilePath);
+ Files.move(clientBuildFileBackup, clientBuildFilePath);
+ }
+
+ Files.deleteIfExists(agentDest);
+ Files.deleteIfExists(workflowDest);
+ }
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/ProcessUtils.java b/Illico/src/main/java/com/github/gilesi/illico/ProcessUtils.java
new file mode 100644
index 00000000..2b55ed7c
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/ProcessUtils.java
@@ -0,0 +1,26 @@
+package com.github.gilesi.illico;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class ProcessUtils {
+ public static int runExternalCommand(String[] command, File directory, org.apache.logging.log4j.Logger logger) throws IOException, InterruptedException {
+ ProcessBuilder pb = new ProcessBuilder(command);
+ if (directory != null) {
+ pb.directory(directory);
+ }
+ pb.redirectErrorStream(true);
+ Process p = pb.start();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ logger.info(line);
+ }
+
+ return p.waitFor();
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/RunResult.java b/Illico/src/main/java/com/github/gilesi/illico/RunResult.java
new file mode 100644
index 00000000..2e153c2e
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/RunResult.java
@@ -0,0 +1,22 @@
+package com.github.gilesi.illico;
+
+import com.github.gilesi.illico.testing.ReportItem;
+
+import java.util.ArrayList;
+
+public class RunResult {
+ public String ProjectId = "";
+ public boolean ProjectIsMissingLibrarySourceCode = false;
+ public boolean ProjectCannotBeReproduced = false; // TODO
+ public boolean ProjectCannotBeInstrumented = false;
+ public boolean ProjectFailedToLoadAgent = false;
+ public boolean ProjectIsMissingTraceFiles = false;
+ public boolean ProjectIsMissingSpecificTraces = false;
+ public boolean ProjectFailedToGenerateAnyTraceTestSrc = false;
+ public boolean ProjectFailedToCompileGeneratedTests = false;
+ public boolean ProjectFailedToExecuteGeneratedTests = false;
+ public boolean ProjectFailedToCompileGeneratedTestsWithNewVersion = false;
+ public boolean ProjectFailedToExecuteGeneratedTestsWithNewVersion = false;
+ public ArrayList TestExecutionReportWithOldVersion;
+ public ArrayList TestExecutionReportWithNewVersion;
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/ConfigurationUtils.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/ConfigurationUtils.java
new file mode 100644
index 00000000..3023c5da
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/ConfigurationUtils.java
@@ -0,0 +1,31 @@
+package com.github.gilesi.illico.buildsystem.configuration.maven;
+
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+public class ConfigurationUtils {
+ public static Xpp3Dom getDomValueObject(String name, String value) {
+ Xpp3Dom domValue = new Xpp3Dom(name);
+ domValue.setValue(value);
+ return domValue;
+ }
+
+ public static Xpp3Dom getDomArrayObject(String arrayName, String name, String[] values) {
+ Xpp3Dom valuesDom = new Xpp3Dom(arrayName);
+ for (String val : values) {
+ addStringValueIfNotEmpty(valuesDom, name, val);
+ }
+ return valuesDom;
+ }
+
+ public static void addStringValueIfNotEmpty(Xpp3Dom config, String name, String value) {
+ if (value != null && !value.isEmpty()) {
+ config.addChild(getDomValueObject(name, value));
+ }
+ }
+
+ public static void addStringArrayValueIfNotEmpty(Xpp3Dom config, String arrayName, String name, String[] values) {
+ if (values != null && values.length > 0) {
+ config.addChild(getDomArrayObject(arrayName, name, values));
+ }
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/IPluginConfiguration.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/IPluginConfiguration.java
new file mode 100644
index 00000000..2e567e78
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/IPluginConfiguration.java
@@ -0,0 +1,7 @@
+package com.github.gilesi.illico.buildsystem.configuration.maven;
+
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+public interface IPluginConfiguration {
+ Xpp3Dom serialize();
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/PomHelper.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/PomHelper.java
new file mode 100644
index 00000000..f2e53748
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/PomHelper.java
@@ -0,0 +1,154 @@
+package com.github.gilesi.illico.buildsystem.configuration.maven;
+
+import com.github.gilesi.illico.Constants;
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class PomHelper {
+
+ public static Model readPomFile(String pomFilePath) throws IOException, XmlPullParserException {
+ MavenXpp3Reader pomReader = new MavenXpp3Reader();
+ Path pomPath = Path.of(pomFilePath);
+ InputStream pomStream = Files.newInputStream(pomPath);
+ Model pomModel = pomReader.read(pomStream);
+ pomModel.setPomFile(new File(pomFilePath));
+ pomStream.close();
+ return pomModel;
+ }
+
+ private static void writePomFile(Model pomModel, String pomFilePath) throws IOException {
+ MavenXpp3Writer pomWriter = new MavenXpp3Writer();
+ FileWriter pomWriteStream = new FileWriter(pomFilePath);
+ pomWriter.write(pomWriteStream, pomModel);
+ pomWriteStream.close();
+ }
+
+ private static boolean doesBuildContainPluginGroupId(Build bld, String groupId) {
+ for (Plugin plg : bld.getPlugins()) {
+ if (plg.getGroupId().equalsIgnoreCase(groupId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean doesModelContainDependencyGroupId(Model pomModel, String groupId) {
+ for (Dependency plg : pomModel.getDependencies()) {
+ if (plg.getGroupId().equalsIgnoreCase(groupId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static Plugin getPluginByGroupId(Build bld, String groupId) throws FileNotFoundException {
+ for (Plugin plg : bld.getPlugins()) {
+ if (plg.getGroupId().equalsIgnoreCase(groupId)) {
+ return plg;
+ }
+ }
+ throw new FileNotFoundException();
+ }
+
+ private static Xpp3Dom buildSureFireConfiguration() {
+ SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration();
+ return sureFireConfiguration.serialize();
+ }
+
+ private static Xpp3Dom buildSureFireConfiguration(String argLine) {
+ SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration();
+ sureFireConfiguration.setArgLine(argLine);
+ return sureFireConfiguration.serialize();
+ }
+
+ private static Plugin getSureFirePlugin() {
+ return buildPlugin(Constants.SUREFIRE_GROUPID, Constants.SUREFIRE_ARTIFACTID, Constants.SUREFIRE_VERSION);
+ }
+
+ private static Dependency buildDependency(String groupId, String artifactId, String version) {
+ Dependency dependency = new Dependency();
+ dependency.setGroupId(groupId);
+ dependency.setArtifactId(artifactId);
+ dependency.setVersion(version);
+ return dependency;
+ }
+
+ private static Plugin buildPlugin(String groupId, String artifactId, String version) {
+ Plugin plugin = new Plugin();
+ plugin.setGroupId(groupId);
+ plugin.setArtifactId(artifactId);
+ plugin.setVersion(version);
+ return plugin;
+ }
+
+ private static Plugin buildSureFirePlugin() {
+ Plugin surePlugin = getSureFirePlugin();
+ Xpp3Dom config = buildSureFireConfiguration();
+ surePlugin.setConfiguration(config);
+ return surePlugin;
+ }
+
+ private static Plugin buildSureFirePlugin(String argLine) {
+ Plugin surePlugin = getSureFirePlugin();
+ Xpp3Dom config = buildSureFireConfiguration(argLine);
+ surePlugin.setConfiguration(config);
+ return surePlugin;
+ }
+
+ private static void removeExistingPlugin(Build bld, String groupId) throws FileNotFoundException {
+ // Get rid of all previous instances of the plugin in case one already exists
+ while (doesBuildContainPluginGroupId(bld, groupId)) {
+ bld.removePlugin(getPluginByGroupId(bld, groupId));
+ }
+ }
+
+ private static void removeExistingSureFirePlugins(Build bld) throws FileNotFoundException {
+ removeExistingPlugin(bld, Constants.SUREFIRE_GROUPID);
+ }
+
+ public static void addSureFire(String pomFilePath) throws XmlPullParserException, IOException {
+ Model pomModel = readPomFile(pomFilePath);
+ Build bld = pomModel.getBuild();
+
+ if (bld == null) {
+ bld = new Build();
+ }
+
+ removeExistingSureFirePlugins(bld);
+
+ Plugin surePlugin = buildSureFirePlugin();
+
+ bld.addPlugin(surePlugin);
+
+ pomModel.setBuild(bld);
+ writePomFile(pomModel, pomFilePath);
+ }
+
+ public static void addSureFire(String pomFilePath, String argLine) throws XmlPullParserException, IOException {
+ Model pomModel = readPomFile(pomFilePath);
+ Build bld = pomModel.getBuild();
+
+ if (bld == null) {
+ bld = new Build();
+ }
+
+ removeExistingSureFirePlugins(bld);
+
+ Plugin surePlugin = buildSureFirePlugin(argLine);
+
+ bld.addPlugin(surePlugin);
+
+ pomModel.setBuild(bld);
+ writePomFile(pomModel, pomFilePath);
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/SureFirePluginConfiguration.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/SureFirePluginConfiguration.java
new file mode 100644
index 00000000..7498ff38
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/SureFirePluginConfiguration.java
@@ -0,0 +1,103 @@
+package com.github.gilesi.illico.buildsystem.configuration.maven;
+
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+public class SureFirePluginConfiguration implements IPluginConfiguration {
+ private int forkCount = 1;
+ private boolean reuseForks = false;
+ private String argLine = "-Xmx2G -XX:MaxMetaspaceSize=2G";
+ private String parallel = "methods";
+ private int threadCount = 1;
+ private String forkedProcessTimeoutInSeconds = "40";
+ private String forkedProcessExitTimeoutInSeconds = "40";
+ private String parallelTestsTimeoutInSeconds = "30";
+ private String parallelTestsTimeoutForcedInSeconds = "30";
+
+ public int getForkCount() {
+ return forkCount;
+ }
+
+ public void setForkCount(int forkCount) {
+ this.forkCount = forkCount;
+ }
+
+ public int getThreadCount() {
+ return threadCount;
+ }
+
+ public void setThreadCount(int threadCount) {
+ this.threadCount = threadCount;
+ }
+
+ public String getArgLine() {
+ return argLine;
+ }
+
+ public void setArgLine(String argLine) {
+ this.argLine = argLine;
+ }
+
+ public String getForkedProcessExitTimeoutInSeconds() {
+ return forkedProcessExitTimeoutInSeconds;
+ }
+
+ public void setForkedProcessExitTimeoutInSeconds(String forkedProcessExitTimeoutInSeconds) {
+ this.forkedProcessExitTimeoutInSeconds = forkedProcessExitTimeoutInSeconds;
+ }
+
+ public String getForkedProcessTimeoutInSeconds() {
+ return forkedProcessTimeoutInSeconds;
+ }
+
+ public void setForkedProcessTimeoutInSeconds(String forkedProcessTimeoutInSeconds) {
+ this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
+ }
+
+ public String getParallelTestsTimeoutForcedInSeconds() {
+ return parallelTestsTimeoutForcedInSeconds;
+ }
+
+ public void setParallelTestsTimeoutForcedInSeconds(String parallelTestsTimeoutForcedInSeconds) {
+ this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds;
+ }
+
+ public String getParallelTestsTimeoutInSeconds() {
+ return parallelTestsTimeoutInSeconds;
+ }
+
+ public void setParallelTestsTimeoutInSeconds(String parallelTestsTimeoutInSeconds) {
+ this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds;
+ }
+
+ public String getParallel() {
+ return parallel;
+ }
+
+ public void setParallel(String parallel) {
+ this.parallel = parallel;
+ }
+
+ public boolean getReuseForks() {
+ return reuseForks;
+ }
+
+ public void setReuseForks(boolean reuseForks) {
+ this.reuseForks = reuseForks;
+ }
+
+ public Xpp3Dom serialize() {
+ Xpp3Dom config = new Xpp3Dom("configuration");
+
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "forkCount", Integer.toString(forkCount));
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "reuseForks", Boolean.toString(reuseForks));
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "argLine", argLine);
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "parallel", parallel);
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "threadCount", Integer.toString(threadCount));
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessTimeoutInSeconds", forkedProcessTimeoutInSeconds);
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessExitTimeoutInSeconds", forkedProcessExitTimeoutInSeconds);
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutInSeconds", parallelTestsTimeoutInSeconds);
+ ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutForcedInSeconds", parallelTestsTimeoutForcedInSeconds);
+
+ return config;
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ConsoleLogger.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ConsoleLogger.java
new file mode 100644
index 00000000..18e4c5d4
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ConsoleLogger.java
@@ -0,0 +1,248 @@
+package com.github.gilesi.illico.runners.maven;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+
+import org.apache.maven.shared.invoker.InvocationOutputHandler;
+import org.apache.maven.shared.invoker.InvokerLogger;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+
+/**
+ * Offers a logger that writes to a print stream like {@link java.lang.System#out}.
+ *
+ * @since 2.0.9
+ */
+public class ConsoleLogger implements InvokerLogger, InvocationOutputHandler {
+
+ /**
+ * The threshold used to filter messages.
+ */
+ private int threshold;
+
+ /**
+ * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}.
+ */
+ public ConsoleLogger() {
+ this(INFO);
+ }
+
+ /**
+ * Creates a new logger that writes to the specified print stream.
+ *
+ * @param threshold The threshold for the logger.
+ */
+ public ConsoleLogger(int threshold) {
+ setThreshold(threshold);
+ }
+
+ /**
+ * Writes the specified message and exception to the print stream.
+ *
+ * @param level The priority level of the message.
+ * @param message The message to log, may be null.
+ * @param error The exception to log, may be null.
+ */
+ private void log(int level, String message, Throwable error) {
+ if (level > threshold) {
+ // don't log when it doesn't match your threshold.
+ return;
+ }
+
+ if (message == null && error == null) {
+ // don't log when there's nothing to log.
+ return;
+ }
+
+ StringBuilder buffer = new StringBuilder();
+ String logLevel = "INFO";
+
+ switch (level) {
+ case (DEBUG) -> logLevel = "DEBUG";
+ case (INFO) -> logLevel = "INFO";
+ case (WARN) -> logLevel = "WARN";
+ case (ERROR) -> logLevel = "ERROR";
+ case (FATAL) -> logLevel = "FATAL";
+ default -> {
+ }
+ }
+
+ buffer.append("[Maven] ");
+
+ if (message != null) {
+ buffer.append(message);
+ }
+
+ if (error != null) {
+ StringWriter writer = new StringWriter();
+ PrintWriter pWriter = new PrintWriter(writer);
+
+ error.printStackTrace(pWriter);
+
+ if (message != null) {
+ buffer.append('\n');
+ }
+
+ buffer.append("Error:\n");
+ buffer.append(writer);
+ }
+
+ System.out.printf("[%s] [%s] %s%n", new Date(), logLevel, buffer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(String message) {
+ log(DEBUG, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(String message, Throwable throwable) {
+ log(DEBUG, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(String message) {
+ log(INFO, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(String message, Throwable throwable) {
+ log(INFO, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(String message) {
+ log(WARN, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(String message, Throwable throwable) {
+ log(WARN, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(String message) {
+ log(ERROR, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(String message, Throwable throwable) {
+ log(ERROR, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void fatalError(String message) {
+ log(FATAL, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void fatalError(String message, Throwable throwable) {
+ log(FATAL, message, throwable);
+ }
+
+ /**
+ * isDebugEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isDebugEnabled() {
+ return threshold >= DEBUG;
+ }
+
+ /**
+ * isErrorEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isErrorEnabled() {
+ return threshold >= ERROR;
+ }
+
+ /**
+ * isFatalErrorEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isFatalErrorEnabled() {
+ return threshold >= FATAL;
+ }
+
+ /**
+ * isInfoEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isInfoEnabled() {
+ return threshold >= INFO;
+ }
+
+ /**
+ * isWarnEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isWarnEnabled() {
+ return threshold >= WARN;
+ }
+
+ /**
+ * Getter for the field threshold.
+ *
+ * @return an int.
+ */
+ public int getThreshold() {
+ return threshold;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setThreshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void consumeLine(String line) {
+ log(INFO, line, null);
+ }
+}
\ No newline at end of file
diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/Log4JLogger.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/Log4JLogger.java
new file mode 100644
index 00000000..d7fb41a7
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/Log4JLogger.java
@@ -0,0 +1,271 @@
+package com.github.gilesi.illico.runners.maven;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.maven.shared.invoker.InvocationOutputHandler;
+import org.apache.maven.shared.invoker.InvokerLogger;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Offers a logger that writes to a print stream like {@link java.lang.System#out}.
+ *
+ * @since 2.0.9
+ */
+public class Log4JLogger implements InvokerLogger, InvocationOutputHandler {
+
+ /**
+ * The print stream to write to, never null.
+ */
+ private final Logger out;
+
+ /**
+ * The threshold used to filter messages.
+ */
+ private int threshold;
+
+ private String prefix;
+
+ /**
+ * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}.
+ */
+ public Log4JLogger() {
+ this(LogManager.getLogger(), INFO, "");
+ }
+
+ public Log4JLogger(String prefix) {
+ this(LogManager.getLogger(), INFO, prefix);
+ }
+
+ /**
+ * Creates a new logger that writes to the specified print stream.
+ *
+ * @param out The print stream to write to, must not be null.
+ * @param threshold The threshold for the logger.
+ */
+ public Log4JLogger(Logger out, int threshold, String prefix) {
+ if (out == null) {
+ throw new NullPointerException("missing output stream");
+ }
+ this.prefix = prefix;
+ this.out = out;
+ setThreshold(threshold);
+ }
+
+ /**
+ * Writes the specified message and exception to the print stream.
+ *
+ * @param level The priority level of the message.
+ * @param message The message to log, may be null.
+ * @param error The exception to log, may be null.
+ */
+ private void log(int level, String message, Throwable error) {
+ if (level > threshold) {
+ // don't log when it doesn't match your threshold.
+ return;
+ }
+
+ if (message == null && error == null) {
+ // don't log when there's nothing to log.
+ return;
+ }
+
+ StringBuilder buffer = new StringBuilder();
+ Level logLevel = Level.INFO;
+
+ switch (level) {
+ case (DEBUG) -> logLevel = Level.DEBUG;
+ case (INFO) -> logLevel = Level.INFO;
+ case (WARN) -> logLevel = Level.WARN;
+ case (ERROR) -> logLevel = Level.ERROR;
+ case (FATAL) -> logLevel = Level.FATAL;
+ default -> {
+ }
+ }
+
+ if (prefix != null && !prefix.isEmpty()) {
+ buffer.append("[%s-Maven] ".formatted(prefix));
+ } else {
+ buffer.append("[Maven] ");
+ }
+
+ if (message != null) {
+ buffer.append(message);
+ }
+
+ if (error != null) {
+ StringWriter writer = new StringWriter();
+ PrintWriter pWriter = new PrintWriter(writer);
+
+ error.printStackTrace(pWriter);
+
+ if (message != null) {
+ buffer.append('\n');
+ }
+
+ buffer.append("Error:\n");
+ buffer.append(writer);
+ }
+
+ out.log(logLevel, buffer.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(String message) {
+ log(DEBUG, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(String message, Throwable throwable) {
+ log(DEBUG, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(String message) {
+ log(INFO, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(String message, Throwable throwable) {
+ log(INFO, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(String message) {
+ log(WARN, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(String message, Throwable throwable) {
+ log(WARN, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(String message) {
+ log(ERROR, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(String message, Throwable throwable) {
+ log(ERROR, message, throwable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void fatalError(String message) {
+ log(FATAL, message, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void fatalError(String message, Throwable throwable) {
+ log(FATAL, message, throwable);
+ }
+
+ /**
+ * isDebugEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isDebugEnabled() {
+ return threshold >= DEBUG;
+ }
+
+ /**
+ * isErrorEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isErrorEnabled() {
+ return threshold >= ERROR;
+ }
+
+ /**
+ * isFatalErrorEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isFatalErrorEnabled() {
+ return threshold >= FATAL;
+ }
+
+ /**
+ * isInfoEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isInfoEnabled() {
+ return threshold >= INFO;
+ }
+
+ /**
+ * isWarnEnabled.
+ *
+ * @return a boolean.
+ */
+ public boolean isWarnEnabled() {
+ return threshold >= WARN;
+ }
+
+ /**
+ * Getter for the field threshold.
+ *
+ * @return an int.
+ */
+ public int getThreshold() {
+ return threshold;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setThreshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void consumeLine(String line) {
+ log(INFO, line, null);
+ }
+}
\ No newline at end of file
diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ProjectRunner.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ProjectRunner.java
new file mode 100644
index 00000000..1edcb010
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ProjectRunner.java
@@ -0,0 +1,181 @@
+package com.github.gilesi.illico.runners.maven;
+
+import com.github.gilesi.illico.Constants;
+import com.github.gilesi.illico.Main;
+import org.apache.maven.model.*;
+import org.apache.maven.shared.invoker.*;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import static com.github.gilesi.illico.buildsystem.configuration.maven.PomHelper.readPomFile;
+
+public class ProjectRunner {
+
+ public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties,
+ boolean batchMode, String userSettingsFilePath, Object logger, String javaToolOptions)
+ throws MavenInvocationException {
+
+ String javaVersion = "8";
+
+ try {
+ Model pomModel = readPomFile(pomFilePath);
+ Properties props = pomModel.getProperties();
+ if (props.stringPropertyNames().contains("java.compiler.version")) {
+ javaVersion = props.getProperty("java.compiler.version");
+ Main.logger.info("Debug: Using java.compiler.version=" + javaVersion);
+ } else if (props.stringPropertyNames().contains("maven.compiler.source")) {
+ javaVersion = props.getProperty("maven.compiler.source");
+ Main.logger.info("Debug: Using maven.compiler.source=" + javaVersion);
+ } else if (props.stringPropertyNames().contains("maven.compiler.target")) {
+ javaVersion = props.getProperty("maven.compiler.target");
+ Main.logger.info("Debug: Using maven.compiler.target=" + javaVersion);
+ } else if (props.stringPropertyNames().contains("maven.compile.source")) {
+ javaVersion = props.getProperty("maven.compile.source");
+ Main.logger.info("Debug: Using maven.compile.source=" + javaVersion);
+ } else if (props.stringPropertyNames().contains("maven.compile.target")) {
+ javaVersion = props.getProperty("maven.compile.target");
+ Main.logger.info("Debug: Using maven.compile.target=" + javaVersion);
+ }
+ } catch (Exception ignored) {
+ Main.logger.info("Debug: Getting java specific version unfortunately failed.");
+ }
+
+ return runPomGoals(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaVersion, logger,
+ javaToolOptions);
+ }
+
+ public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties,
+ boolean batchMode, String userSettingsFilePath, String javaVersion, Object logger, String javaToolOptions)
+ throws MavenInvocationException {
+ String javaHome = Constants.JAVA_VERSIONS.getOrDefault(javaVersion, System.getenv("JAVA_HOME"));
+ return runPomGoalsInternal(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaHome,
+ javaVersion, logger, javaToolOptions);
+ }
+
+ public static int runPomGoalsInternal(String pomFilePath, List pomGoals, List pomProperties,
+ boolean batchMode, String userSettingsFilePath, String JavaHome, String JavaVersion, Object logger,
+ String javaToolOptions) throws MavenInvocationException {
+ File pomFile = new File(pomFilePath);
+
+ InvokerLogger invokerLogger = null;
+ InvocationOutputHandler invocationOutputHandler = null;
+
+ if (logger instanceof InvokerLogger tmp) {
+ invokerLogger = tmp;
+ }
+
+ if (logger instanceof InvocationOutputHandler tmp) {
+ invocationOutputHandler = tmp;
+ }
+
+ File projectDirectory = new File(Path.of(pomFilePath).getParent().toString());
+
+ Properties properties = new Properties();
+ pomProperties.forEach(p -> properties.setProperty(p, "true"));
+
+ properties.setProperty("maven.compiler.source", JavaVersion);
+ properties.setProperty("maven.compiler.target", JavaVersion);
+ properties.setProperty("maven.compile.source", JavaVersion);
+ properties.setProperty("maven.compile.target", JavaVersion);
+
+ properties.setProperty("maven.source.skip", "true");
+ properties.setProperty("assembly.skipAssembly", "true");
+ properties.setProperty("shade.skip", "true");
+ properties.setProperty("maven.war.skip", "true");
+ properties.setProperty("maven.rar.skip", "true");
+ properties.setProperty("changelog.skip", "true");
+ properties.setProperty("checkstyle.skip", "true");
+ properties.setProperty("maven.doap.skip", "true");
+ properties.setProperty("maven.javadoc.skip", "true");
+ properties.setProperty("maven.jxr.skip", "true");
+ properties.setProperty("linkcheck.skip", "true");
+ properties.setProperty("pmd.skip", "true");
+ properties.setProperty("mpir.skip", "true");
+ properties.setProperty("gpg.skip", "true");
+ properties.setProperty("jdepend.skip", "true");
+
+ String javacLocation = "%s%sbin%sjavac".formatted(JavaHome, File.separator, File.separator);
+ pomGoals.add("-Dmaven.compiler.fork=true");
+ if (Files.exists(Path.of(javacLocation))) {
+ pomGoals.add("-Dmaven.compiler.executable=%s".formatted(javacLocation));
+ }
+
+ InvocationRequest request = new DefaultInvocationRequest();
+
+ Integer timeout = 30 * 60; // 30 minutes in seconds
+
+ request.setShellEnvironmentInherited(false);
+ request.setPomFile(pomFile);
+ request.setGoals(pomGoals);
+ request.setProperties(properties);
+ request.setAlsoMake(true);
+ request.setBatchMode(true);
+ request.setTimeoutInSeconds(timeout);
+
+ request.setJavaHome(new File(JavaHome));
+ request.setBaseDirectory(projectDirectory);
+ request.setInputStream(InputStream.nullInputStream());
+ if (invocationOutputHandler != null) {
+ request.setOutputHandler(invocationOutputHandler);
+ request.setErrorHandler(invocationOutputHandler);
+ }
+
+ if (userSettingsFilePath != null && !userSettingsFilePath.isEmpty() && !userSettingsFilePath.isBlank()) {
+ Path pa = Path.of(userSettingsFilePath);
+ if (Files.exists(pa) && Files.isRegularFile(pa)) {
+ request.setUserSettingsFile(new File(userSettingsFilePath));
+ }
+ }
+
+ if (javaToolOptions != null && !javaToolOptions.isEmpty()) {
+ request.addShellEnvironment("JAVA_TOOL_OPTIONS", javaToolOptions);
+ }
+
+ Main.logger.info("Building with pom=%s goals=%s properties=%s%n"
+ .formatted(pomFilePath,
+ pomGoals,
+ properties));
+
+ try {
+ Invoker invoker = new DefaultInvoker();
+ if (invokerLogger != null) {
+ invoker.setLogger(invokerLogger);
+ }
+
+ invoker.setMavenHome(new File(System.getenv("MAVEN_HOME")));
+
+ InvocationResult result = invoker.execute(request);
+
+ Main.logger.info("Building with pom={} goals={} properties={} returned {}",
+ pomFile,
+ pomGoals,
+ properties,
+ result.getExitCode());
+
+ return result.getExitCode();
+ } catch (MavenInvocationException e) {
+ throw e;
+ }
+ }
+
+ public static boolean runMavenGoalOnRepository(String repositoryDirectory, String goal, String userSettingsFilePath,
+ String javaVersion, Object logger, String javaToolOptions) throws MavenInvocationException {
+ Main.logger.info("ExecuteMavenGoalOnDuetsRepository Entry");
+ String pomFilePath = "%s%spom.xml".formatted(repositoryDirectory, File.separator);
+ ArrayList goals = new ArrayList<>();
+ goals.add(goal);
+
+ if (javaVersion != null && !javaVersion.isBlank()) {
+ return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, javaVersion, logger,
+ javaToolOptions) == 0;
+ } else {
+ return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, logger,
+ javaToolOptions) == 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/TestAssertedLogger.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/TestAssertedLogger.java
new file mode 100644
index 00000000..ced5965f
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/TestAssertedLogger.java
@@ -0,0 +1,192 @@
+package com.github.gilesi.illico.runners.maven;
+
+import org.apache.maven.shared.invoker.InvocationOutputHandler;
+import org.apache.maven.shared.invoker.InvokerLogger;
+
+import java.io.IOException;
+
+public class TestAssertedLogger implements InvokerLogger, InvocationOutputHandler {
+
+ private InvokerLogger invokerLogger = null;
+ private InvocationOutputHandler invocationOutputHandler = null;
+ private boolean testFailed = false;
+
+ public TestAssertedLogger(Object logger) {
+ if (logger instanceof InvokerLogger tmp) {
+ invokerLogger = tmp;
+ }
+
+ if (logger instanceof InvocationOutputHandler tmp) {
+ invocationOutputHandler = tmp;
+ }
+ }
+
+
+ public void debug(String message) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.debug(message);
+ }
+ }
+
+ public void debug(String message, Throwable throwable) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.debug(message, throwable);
+ }
+ }
+
+ public boolean isDebugEnabled() {
+ if (invokerLogger != null) {
+ return invokerLogger.isDebugEnabled();
+ }
+
+ return false;
+ }
+
+ public void info(String message) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.info(message);
+ }
+ }
+
+ public void info(String message, Throwable throwable) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.info(message, throwable);
+ }
+ }
+
+ public boolean isInfoEnabled() {
+ if (invokerLogger != null) {
+ return invokerLogger.isInfoEnabled();
+ }
+
+ return false;
+ }
+
+ public void warn(String message) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.warn(message);
+ }
+ }
+
+ public void warn(String message, Throwable throwable) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.warn(message, throwable);
+ }
+ }
+
+ public boolean isWarnEnabled() {
+ if (invokerLogger != null) {
+ return invokerLogger.isWarnEnabled();
+ }
+
+ return false;
+ }
+
+ public void error(String message) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.error(message);
+ }
+ }
+
+ public void error(String message, Throwable throwable) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.error(message, throwable);
+ }
+ }
+
+ public boolean isErrorEnabled() {
+ if (invokerLogger != null) {
+ return invokerLogger.isErrorEnabled();
+ }
+
+ return false;
+ }
+
+ public void fatalError(String message) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.fatalError(message);
+ }
+ }
+
+ public void fatalError(String message, Throwable throwable) {
+ if (message.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invokerLogger != null) {
+ invokerLogger.fatalError(message, throwable);
+ }
+ }
+
+ public boolean isFatalErrorEnabled() {
+ if (invokerLogger != null) {
+ return invokerLogger.isFatalErrorEnabled();
+ }
+
+ return false;
+ }
+
+ public int getThreshold() {
+ if (invokerLogger != null) {
+ return invokerLogger.getThreshold();
+ }
+
+ return 0;
+ }
+
+ public void setThreshold(int threshold) {
+ if (invokerLogger != null) {
+ invokerLogger.setThreshold(threshold);
+ }
+ }
+
+ public void consumeLine(String line) throws IOException {
+ if (line.contains("<<< FAILURE!")) {
+ testFailed = true;
+ }
+
+ if (invocationOutputHandler != null) {
+ invocationOutputHandler.consumeLine(line);
+ }
+ }
+
+ public boolean HasTestFailed() {
+ return testFailed;
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/ReportItem.java b/Illico/src/main/java/com/github/gilesi/illico/testing/ReportItem.java
new file mode 100644
index 00000000..459c6778
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/ReportItem.java
@@ -0,0 +1,65 @@
+package com.github.gilesi.illico.testing;
+
+import com.github.gilesi.illico.Constants;
+import com.github.gilesi.illico.testing.models.Testcase;
+
+public class ReportItem {
+ public String Name;
+ public String Version;
+ private String TestName;
+ private String TestResult;
+ private String ErrorDetails;
+
+ public ReportItem(String projectName, String projectCommit, Testcase testcase) {
+ this.TestName = "%s.%s".formatted(testcase.ClassName, testcase.Name);
+ this.TestResult = ((testcase.Error != null) || (testcase.Failure != null)) ? Constants.RED_TEST : Constants.GREEN_TEST;
+ this.ErrorDetails = (testcase.Error != null) ? testcase.Error.Message : "";
+ this.Name = projectName;
+ this.Version = projectCommit;
+ }
+
+ public String getName() {
+ return Name;
+ }
+
+ public void setName(String name) {
+ Name = name;
+ }
+
+ public String getVersion() {
+ return Version;
+ }
+
+ public void setVersion(String version) {
+ Version = version;
+ }
+
+ public String getTestName() {
+ return TestName;
+ }
+
+ public void setTestName(String testName) {
+ TestName = testName;
+ }
+
+ public String getTestResult() {
+ return TestResult;
+ }
+
+ public void setTestResult(String testResult) {
+ TestResult = testResult;
+ }
+
+ public String getErrorDetails() {
+ return ErrorDetails;
+ }
+
+ public void setErrorDetails(String errorDetails) {
+ ErrorDetails = errorDetails;
+ }
+
+ @Override
+ public String toString() {
+ return "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"".formatted(Name, Version, TestName, TestResult, ErrorDetails);
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/XmlParser.java b/Illico/src/main/java/com/github/gilesi/illico/testing/XmlParser.java
new file mode 100644
index 00000000..4929b28b
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/XmlParser.java
@@ -0,0 +1,33 @@
+package com.github.gilesi.illico.testing;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.github.gilesi.illico.testing.models.Testsuite;
+import com.github.gilesi.illico.testing.models.Testsuites;
+
+import java.io.File;
+import java.io.IOException;
+
+public class XmlParser {
+
+ private static final XmlMapper mapper = XmlMapper
+ .builder()
+ .defaultUseWrapper(false)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .build();
+
+ public static Testsuite deserializeTestsuite(String XmlPath) throws IOException {
+ File fi = new File(XmlPath);
+ return mapper.readValue(fi, Testsuite.class);
+ }
+
+ public static Testsuites deserializeTestsuites(String XmlPath) throws IOException {
+ File fi = new File(XmlPath);
+ return mapper.readValue(fi, Testsuites.class);
+ }
+
+ public static void serialize(String XmlPath, Object obj) throws IOException {
+ File fi = new File(XmlPath);
+ mapper.writeValue(fi, obj);
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/gradle/GradleHarness.java b/Illico/src/main/java/com/github/gilesi/illico/testing/gradle/GradleHarness.java
new file mode 100644
index 00000000..53b5b90f
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/gradle/GradleHarness.java
@@ -0,0 +1,52 @@
+package com.github.gilesi.illico.testing.gradle;
+
+import com.github.gilesi.illico.FileUtils;
+import com.github.gilesi.illico.Main;
+import com.github.gilesi.illico.testing.ReportItem;
+import com.github.gilesi.illico.testing.XmlParser;
+import com.github.gilesi.illico.testing.models.Testcase;
+import com.github.gilesi.illico.testing.models.Testsuite;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+
+public class GradleHarness {
+ private static void buildReportXml(String projectName, String projectCommit, String targetLocation,
+ ArrayList reportItems) throws IOException {
+ String sureFireReportsLocation = "%s%stest-results%stest".formatted(targetLocation, File.separator,
+ File.separator);
+ ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false);
+
+ for (String xml : reportXmls) {
+ Testsuite testsuite = XmlParser.deserializeTestsuite(xml);
+ Testcase[] list = testsuite.getTestcase();
+ if (list == null) {
+ continue;
+ }
+ for (Testcase testcase : list) {
+ reportItems.add(new ReportItem(projectName, projectCommit, testcase));
+ }
+ }
+ }
+
+ public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit,
+ String buildGradleFileLocation) {
+ ArrayList reportItems = new ArrayList<>();
+
+ Path buildGradleFilePath = Path.of(buildGradleFileLocation);
+ String targetLocation = "%s%sbuild".formatted(buildGradleFilePath.getParent().toAbsolutePath(), File.separator);
+
+ if (Files.isDirectory(Path.of(targetLocation))) {
+ try {
+ buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems);
+ } catch (Exception e) {
+ Main.logger.info(e);
+ }
+ }
+
+ return reportItems;
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Error.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Error.java
new file mode 100644
index 00000000..c0b06c6c
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Error.java
@@ -0,0 +1,17 @@
+package com.github.gilesi.illico.testing.models;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
+
+public class Error {
+ @JacksonXmlProperty(isAttribute = true, localName = "message")
+ public String Message;
+
+ @JacksonXmlProperty(isAttribute = true, localName = "type")
+ public String Type;
+
+ @JacksonXmlText
+ @JacksonXmlCData
+ private String Data;
+}
\ No newline at end of file
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Properties.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Properties.java
new file mode 100644
index 00000000..5828312b
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Properties.java
@@ -0,0 +1,13 @@
+package com.github.gilesi.illico.testing.models;
+
+public class Properties {
+ private Property[] property;
+
+ public Property[] getProperty() {
+ return property;
+ }
+
+ public void setProperty(Property[] value) {
+ this.property = value;
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Property.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Property.java
new file mode 100644
index 00000000..307faddc
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Property.java
@@ -0,0 +1,22 @@
+package com.github.gilesi.illico.testing.models;
+
+public class Property {
+ private String name;
+ private String value;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
\ No newline at end of file
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testcase.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testcase.java
new file mode 100644
index 00000000..c19eb2ea
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testcase.java
@@ -0,0 +1,23 @@
+package com.github.gilesi.illico.testing.models;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+
+public class Testcase {
+ @JacksonXmlElementWrapper(localName = "error")
+ public com.github.gilesi.illico.testing.models.Error Error;
+
+ @JacksonXmlElementWrapper(localName = "failure")
+ public Error Failure;
+
+ @JacksonXmlElementWrapper(localName = "name")
+ public String Name;
+
+ @JacksonXmlElementWrapper(localName = "classname")
+ public String ClassName;
+
+ @JacksonXmlElementWrapper(localName = "time")
+ public String Time;
+
+ @JacksonXmlElementWrapper(localName = "system-out")
+ public String SystemOut;
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuite.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuite.java
new file mode 100644
index 00000000..3a956c05
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuite.java
@@ -0,0 +1,103 @@
+package com.github.gilesi.illico.testing.models;
+
+public class Testsuite {
+ private Properties properties;
+ private Testcase[] testcase;
+ private String xmlnsXsi;
+ private String xsiNoNamespaceSchemaLocation;
+ private String version;
+ private String name;
+ private String time;
+ private String tests;
+ private String errors;
+ private String skipped;
+ private String failures;
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ public void setProperties(Properties value) {
+ this.properties = value;
+ }
+
+ public Testcase[] getTestcase() {
+ return testcase;
+ }
+
+ public void setTestcase(Testcase[] value) {
+ this.testcase = value;
+ }
+
+ public String getXmlnsXsi() {
+ return xmlnsXsi;
+ }
+
+ public void setXmlnsXsi(String value) {
+ this.xmlnsXsi = value;
+ }
+
+ public String getXsiNoNamespaceSchemaLocation() {
+ return xsiNoNamespaceSchemaLocation;
+ }
+
+ public void setXsiNoNamespaceSchemaLocation(String value) {
+ this.xsiNoNamespaceSchemaLocation = value;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String value) {
+ this.version = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ public String getTime() {
+ return time;
+ }
+
+ public void setTime(String value) {
+ this.time = value;
+ }
+
+ public String getTests() {
+ return tests;
+ }
+
+ public void setTests(String value) {
+ this.tests = value;
+ }
+
+ public String getErrors() {
+ return errors;
+ }
+
+ public void setErrors(String value) {
+ this.errors = value;
+ }
+
+ public String getSkipped() {
+ return skipped;
+ }
+
+ public void setSkipped(String value) {
+ this.skipped = value;
+ }
+
+ public String getFailures() {
+ return failures;
+ }
+
+ public void setFailures(String value) {
+ this.failures = value;
+ }
+}
\ No newline at end of file
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuites.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuites.java
new file mode 100644
index 00000000..1114e392
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuites.java
@@ -0,0 +1,13 @@
+package com.github.gilesi.illico.testing.models;
+
+public class Testsuites {
+ private Testsuite[] testsuites;
+
+ public Testsuite[] getTestsuites() {
+ return testsuites;
+ }
+
+ public void setTestsuites(Testsuite[] value) {
+ this.testsuites = value;
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/surefire/SurefireHarness.java b/Illico/src/main/java/com/github/gilesi/illico/testing/surefire/SurefireHarness.java
new file mode 100644
index 00000000..f077354c
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/testing/surefire/SurefireHarness.java
@@ -0,0 +1,49 @@
+package com.github.gilesi.illico.testing.surefire;
+
+import com.github.gilesi.illico.FileUtils;
+import com.github.gilesi.illico.Main;
+import com.github.gilesi.illico.testing.ReportItem;
+import com.github.gilesi.illico.testing.models.Testcase;
+import com.github.gilesi.illico.testing.models.Testsuite;
+import com.github.gilesi.illico.testing.XmlParser;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+
+public class SurefireHarness {
+ private static void buildReportXml(String projectName, String projectCommit, String targetLocation, ArrayList reportItems) throws IOException {
+ String sureFireReportsLocation = "%s%ssurefire-reports".formatted(targetLocation, File.separator);
+ ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false);
+
+ for (String xml : reportXmls) {
+ Testsuite testsuite = XmlParser.deserializeTestsuite(xml);
+ Testcase[] list = testsuite.getTestcase();
+ if (list == null) {
+ continue;
+ }
+ for (Testcase testcase : list) {
+ reportItems.add(new ReportItem(projectName, projectCommit, testcase));
+ }
+ }
+ }
+
+ public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit, String pomFileLocation) {
+ ArrayList reportItems = new ArrayList<>();
+
+ Path pomFilePath = Path.of(pomFileLocation);
+ String targetLocation = "%s%starget".formatted(pomFilePath.getParent().toAbsolutePath(), File.separator);
+
+ if (Files.isDirectory(Path.of(targetLocation))) {
+ try {
+ buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems);
+ } catch (Exception e) {
+ Main.logger.info(e);
+ }
+ }
+
+ return reportItems;
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/tools/ConfGen.java b/Illico/src/main/java/com/github/gilesi/illico/tools/ConfGen.java
new file mode 100644
index 00000000..489cc0af
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/tools/ConfGen.java
@@ -0,0 +1,23 @@
+package com.github.gilesi.illico.tools;
+
+import com.github.gilesi.illico.Constants;
+import com.github.gilesi.illico.Main;
+import com.github.gilesi.illico.ProcessUtils;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class ConfGen {
+ public static void runConfGen(String GilesiRepositoryLocation, String libraryConfig, String outputTestProject, String libraryLocation) throws IOException, InterruptedException {
+ ProcessUtils.runExternalCommand(new String[]{
+ Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(),
+ "-Xmx8g",
+ "-Xms8g",
+ "-jar",
+ Path.of(GilesiRepositoryLocation).resolve(Constants.CONF_GEN_LOCATION).toString(),
+ libraryConfig,
+ outputTestProject,
+ libraryLocation
+ }, null, Main.loggerConfGen);
+ }
+}
diff --git a/Illico/src/main/java/com/github/gilesi/illico/tools/TestGen.java b/Illico/src/main/java/com/github/gilesi/illico/tools/TestGen.java
new file mode 100644
index 00000000..289ee991
--- /dev/null
+++ b/Illico/src/main/java/com/github/gilesi/illico/tools/TestGen.java
@@ -0,0 +1,44 @@
+package com.github.gilesi.illico.tools;
+
+import com.github.gilesi.illico.Constants;
+import com.github.gilesi.illico.Main;
+import com.github.gilesi.illico.ProcessUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class TestGen {
+ public static final Logger logger = LogManager.getLogger();
+
+ public static void runTestGen(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException {
+ ProcessUtils.runExternalCommand(new String[]{
+ Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(),
+ "-Xmx8g",
+ "-Xms8g",
+ "-jar",
+ Path.of(GilesiRepositoryLocation).resolve(Constants.TEST_GEN_LOCATION).toString(),
+ traceFile,
+ outputCodeDirectory,
+ libraryConfig
+ }, null, Main.loggerTraceGen);
+ }
+
+ public static void runTraceDiff(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException {
+ ProcessUtils.runExternalCommand(new String[]{
+ Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(),
+ "-Xmx32768m",
+ "-Xms16384m",
+ "-Xss1024m",
+ "-XX:ParallelGCThreads=8",
+ "-XX:InitiatingHeapOccupancyPercent=70",
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-jar",
+ Path.of(GilesiRepositoryLocation).resolve(Constants.TRACE_DIFF_LOCATION).toString(),
+ traceFile,
+ outputCodeDirectory,
+ libraryConfig
+ }, null, Main.loggerTraceDiff);
+ }
+}
\ No newline at end of file
diff --git a/Illico/src/main/resources/log4j2.xml b/Illico/src/main/resources/log4j2.xml
new file mode 100644
index 00000000..6ef0deaf
--- /dev/null
+++ b/Illico/src/main/resources/log4j2.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+