From 081ac106f41b9539ec4b21dc8bb342037b62bd2e Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Sat, 17 Aug 2024 21:57:10 +0200 Subject: [PATCH] DependencyResolver rewrite, likely broken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This seems to work, but it probably has lots of bugs as I can't really test it efficiently. Need to write unit tests for this, aw man...                                       So we back in the mine Got our pickaxe swinging from side to side Side-side to side This task, a grueling one Hope to find some diamonds tonight, night, night Diamonds tonight Heads up You hear a sound, turn around and look up Total shock fills your body Oh, no, it's you again I can never forget those eyes, eyes, eyes Eyes-eye-eyes 'Cause, baby, tonight The creeper's tryna steal all our stuff again 'Cause, baby, tonight You grab your pick, shoval and bolt again (Bolt again-gain) And run, run until it's done, done Until the sun comes up in the morn' 'Cause, baby, tonight The creeper's tryna steal all our stuff again (Stuff again-gain) Just when you think you're safe Overhear some hissing from right behind Right-right behind THat's a nice life you have Shame it's gotta end at this time, time, time Time-time-time-time Then your health bar drops and you could use a one-up Get inside, don't be tardy So, now you're stuck in there Half a heart is left, but don't die, die, die Die-die-die 'Cause, baby, tonight The creeper's tryna steal all our stuff again 'Cause, baby, tonight You grab your pick, shovel and bolt again (Bolt again-gain) (Creepers, you're mine, haha) Dig up diamonds and craft those diamonds And make some armor, get it, baby Go and forge that like you so MLG pro The sword's made of diamonds, so come at me, bro, huh Training in your room under the torchlight Hone that form to get you ready for the big fight Every single day and the whole night Creeper's out prowlin', hoo, alright Look at me, look at you Take my revenge, that's what I'm gonna do I'm a warrior, baby, what else is new? And my blade's gonna tear through you, bring it 'Cause, baby, tonight The creeper's tryna steal all our stuff again (Gather your stuff, yeah, let's take back the world) Yeah, baby, tonight (Haha) Grab your sword, armor and go (It's on) Take your revenge (Woo), oh-oh, oh-oh So fight, fight, like it's the last, last night Of your life, life, show them your bite (Woo) 'Cause, baby, tonight The creeper's tryna steal all our stuff again 'Cause, baby, tonight You grab your pick, shovel and bolt again (Bolt again-gain, woo) And run, run until it's done, done Until the sun comes up in the morn' 'Cause, baby, tonight (Come on, swing your sword up high) The creeper's tryna steal all our stuff again (Come on, jab your sword down low) (Woo) --- .../base/utility/DependencyResolver.java | 413 +++++++++++------- 1 file changed, 265 insertions(+), 148 deletions(-) diff --git a/base/src/main/java/de/staropensource/sosengine/base/utility/DependencyResolver.java b/base/src/main/java/de/staropensource/sosengine/base/utility/DependencyResolver.java index df3c173e..be8f5ab1 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/utility/DependencyResolver.java +++ b/base/src/main/java/de/staropensource/sosengine/base/utility/DependencyResolver.java @@ -19,15 +19,17 @@ package de.staropensource.sosengine.base.utility; +import de.staropensource.sosengine.base.classes.VersioningSystem; import de.staropensource.sosengine.base.exceptions.UnexpectedThrowableException; import de.staropensource.sosengine.base.exceptions.dependency.UnmetDependenciesException; +import de.staropensource.sosengine.base.logging.LoggerInstance; import de.staropensource.sosengine.base.types.DependencyVector; -import de.staropensource.sosengine.base.types.immutable.ImmutableArrayList; -import de.staropensource.sosengine.base.types.immutable.ImmutableLinkedList; import lombok.Getter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.lang.reflect.InvocationTargetException; import java.util.*; /** @@ -38,18 +40,33 @@ import java.util.*; @SuppressWarnings({ "unused", "UnusedReturnValue", "JavadocDeclaration" }) public final class DependencyResolver { /** - * A list of {@link DependencyVector}s. + * Contains the {@link LoggerInstance} for this instance. + * + * @see LoggerInstance + * @since v1-alpha4 + */ + private final @NotNull LoggerInstance logger = new LoggerInstance.Builder().setClazz(getClass()).setOrigin("ENGINE").setMetadata(String.valueOf(hashCode())).build(); + + /** + * List of {@link DependencyVector}s to resolve. * * @since v1-alpha1 */ - List vectors = new ArrayList<>(); + Set vectors = new HashSet<>(); /** - * {@code true} if the current {@link DependencyVector} list has been resolved successfully. + * List of identifiers of already resolved vectors. + * + * @since v1-alpha4 + */ + Set<@NotNull String> vectorsResolved = new HashSet<>(); + + /** + * Contains whether the current {@link DependencyVector} list has been resolved successfully. * * @since v1-alpha1 * -- GETTER -- - * Returns {@code true} if the current {@link DependencyVector} list has been resolved successfully. + * Returns whether the current {@link DependencyVector} list has been resolved successfully. * * @return resolved status * @since v1-alpha1 @@ -72,7 +89,9 @@ public final class DependencyResolver { * @since v1-alpha1 */ public synchronized DependencyResolver addVector(@NotNull DependencyVector vector) { - vectors.add(vector); + try { + vectors.add(vector); + } catch (IllegalArgumentException ignored) {} resolved = false; return this; } @@ -84,10 +103,8 @@ public final class DependencyResolver { * @return itself * @since v1-alpha1 */ - public synchronized DependencyResolver addVectors(@NotNull DependencyVector[] vectors) { - addVectors(Arrays.stream(vectors).toList()); - resolved = false; - return this; + public DependencyResolver addVectors(@NotNull DependencyVector[] vectors) { + return addVectors(Arrays.stream(vectors).toList()); } /** @@ -97,61 +114,9 @@ public final class DependencyResolver { * @return itself * @since v1-alpha1 */ - public synchronized DependencyResolver addVectors(@NotNull Collection vectors) { - this.vectors.addAll(vectors); - resolved = false; - return this; - } - - /** - * Adds multiple dependency vectors. - * - * @param vectors dependency vectors to add - * @return itself - * @since v1-alpha1 - */ - public synchronized DependencyResolver addVectors(@NotNull List vectors) { - this.vectors.addAll(vectors); - resolved = false; - return this; - } - - /** - * Adds multiple dependency vectors. - * - * @param vectors dependency vectors to add - * @return itself - * @since v1-alpha1 - */ - public synchronized DependencyResolver addVectors(@NotNull ImmutableArrayList vectors) { - this.vectors.addAll(vectors); - resolved = false; - return this; - } - - /** - * Adds multiple dependency vectors. - * - * @param vectors dependency vectors to add - * @return itself - * @since v1-alpha1 - */ - public synchronized DependencyResolver addVectors(@NotNull ImmutableLinkedList vectors) { - this.vectors.addAll(vectors); - resolved = false; - return this; - } - - /** - * Adds multiple dependency vectors. - * - * @param vectors dependency vectors to add - * @return itself - * @since v1-alpha1 - */ - public synchronized DependencyResolver addVectors(@NotNull Set vectors) { - this.vectors.addAll(vectors); - resolved = false; + public DependencyResolver addVectors(@NotNull Collection vectors) { + for (DependencyVector vector : vectors) // thread-safety + addVector(vector); return this; } @@ -161,100 +126,252 @@ public final class DependencyResolver { * * @return itself * @throws UnmetDependenciesException when dependencies are unmet + * @throws IllegalStateException when encountering an invalid dependency or provider * @throws UnexpectedThrowableException when some unknown error occurs * @since v1-alpha1 */ - @SuppressWarnings("JavaReflectionInvocation") public synchronized DependencyResolver resolve() throws UnmetDependenciesException, UnexpectedThrowableException { Map unmetDependencies = new HashMap<>(); - resolved = false; - try { - for (DependencyVector vector : vectors) - for (String dependency : vector.getDependencies()) { - int match = 0; - - if (dependency.contains("=")) { - String dependencyIdentifier = dependency.substring(0, dependency.indexOf("=")); - String dependencyVersion = dependency.substring(dependency.indexOf("=")); - - for (DependencyVector vectorCheck : vectors) - if (vectorCheck.getIdentifier().equals(dependency)) { - if (vectorCheck.getVersioningSystem().getDeclaredConstructor().newInstance(dependencyVersion).compare(vectorCheck.getVersioningSystem().getDeclaredConstructor().newInstance(vectorCheck.getVersion())) == 1) - match = -1; - else - match = 2; - - break; - } - - if (match == 0) - unmetDependencies.put(vector, "Depends on '" + dependencyIdentifier + "', which is missing"); - else { - unmetDependencies.put(vector, "Depends exactly on '" + dependency + "', which is not installed"); - } - } else if (dependency.contains("<")) { - String dependencyIdentifier = dependency.substring(0, dependency.indexOf("<")); - String dependencyVersion = dependency.substring(dependency.indexOf("<")); - - for (DependencyVector vectorCheck : vectors) - if (vectorCheck.getIdentifier().equals(dependency)) { - if (vectorCheck.getVersioningSystem().getDeclaredConstructor().newInstance(dependencyVersion).compare(vectorCheck.getVersioningSystem().getDeclaredConstructor().newInstance(vectorCheck.getVersion())) == 0) - match = -1; - else - match = 2; - - break; - } - - if (match == 0) - unmetDependencies.put(vector, "Depends on '" + dependencyIdentifier + "', which is missing"); - else { - unmetDependencies.put(vector, "Depends at maximum on '" + dependency + "', which is not installed"); - } - } else if (dependency.contains(">")) { - String dependencyIdentifier = dependency.substring(0, dependency.indexOf(">")); - String dependencyVersion = dependency.substring(dependency.indexOf(">")); - - for (DependencyVector vectorCheck : vectors) - if (vectorCheck.getIdentifier().equals(dependency)) { - if (vectorCheck.getVersioningSystem().getDeclaredConstructor().newInstance(dependencyVersion).compare(vectorCheck.getVersioningSystem().getDeclaredConstructor().newInstance(vectorCheck.getVersion())) == 2) - match = -1; - else - match = 2; - - break; - } - - if (match == 0) - unmetDependencies.put(vector, "Depends on '" + dependencyIdentifier + "', which is missing"); - else { - unmetDependencies.put(vector, "Depends at minimum on '" + dependency + "', which is not installed"); - } - } else { - for (DependencyVector vectorCheck : vectors) - if (vectorCheck.getIdentifier().equals(dependency)) { - match = -1; - break; - } - - if (match == 0) - unmetDependencies.put(vector, "Depends on any version of '" + dependency + "', which is missing"); - } - } - } catch (Exception exception) { - // Throw UnexpectedThrowableException when something horribly fails - throw new UnexpectedThrowableException(exception); + for (DependencyVector vector : vectors) { + if (!vectorsResolved.contains(vector.getIdentifier())) { + resolveVector(vector); + vectorsResolved.add(vector.getIdentifier()); + } } - // Check for any unmet dependencies - if (!unmetDependencies.isEmpty()) - throw new UnmetDependenciesException(unmetDependencies); - resolved = true; return this; } + /** + * Resolves a vector. + * Throws an exception when detecting an unmet dependency or a dependency cycle. + * + * @return list of unmet dependencies + * @throws IllegalStateException when encountering an invalid dependency or provider + * @throws Exception when some unknown error occurs + * @since v1-alpha4 + */ + private @NotNull List<@NotNull String> resolveVector(@NotNull DependencyVector vector) throws IllegalStateException { + List<@NotNull String> unmetDependencies = new ArrayList<>(); + + // provides + + for (String dependency : vector.getDependencies()) { + // 0 = identifier + // 1 = version equal + // 2 = version smaller + // 3 = version bigger + int mode = 0; + boolean[] duplicateCheck = new boolean[3]; + StringBuilder identifier = new StringBuilder(); + StringBuilder versionEqual = new StringBuilder(); + StringBuilder versionSmaller = new StringBuilder(); + StringBuilder versionBigger = new StringBuilder(); + + // Get variables + for (char character : dependency.toCharArray()) { + switch (character) { + case '=' -> { + mode = 1; + continue; + } + case '<' -> { + mode = 2; + continue; + } + case '>' -> { + mode = 3; + continue; + } + } + + switch (mode) { + // Identifier + case 0 -> identifier.append(character); + // Version equal + case 1 -> { + // Check for duplicate + if (character == '=' && duplicateCheck[0]) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" cannot include multiple '=' characters"); + duplicateCheck[0] = true; + + // Check for smaller and bigger than + if (duplicateCheck[1] || duplicateCheck[2]) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" cannot require a specific version and have minimum and maximum version specifiers"); + + versionEqual.append(character); + } + // Version smaller + case 2 -> { + // Check for duplicate + if (character == '<' && duplicateCheck[1]) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" cannot include multiple '<' characters"); + duplicateCheck[1] = true; + + // Check for equal + if (duplicateCheck[0]) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" cannot require a specific version and have minimum and maximum version specifiers"); + + versionSmaller.append(character); + } + // Version bigger + case 3 -> { + // Check for duplicate + if (character == '>' && duplicateCheck[2]) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" cannot include multiple '>' characters"); + duplicateCheck[2] = true; + + // Check for equal + if (duplicateCheck[0]) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" cannot require a specific version and have minimum and maximum version specifiers"); + + versionBigger.append(character); + } + } + } + + // Resolve vector + DependencyVector dependencyResolved = getMatchingVector(identifier.toString()); + if (dependencyResolved == null) { + unmetDependencies.add("Dependency \"" + dependency + "\" is not met: Not found"); + continue; + } + + VersioningSystem versioningSystemResolved; + + // Get resolved versioning system + try { + versioningSystemResolved = dependencyResolved.getVersioningSystem().getDeclaredConstructor(String.class).newInstance(dependencyResolved.getVersion()); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) { + logger.crash("Unable to check version of dependency \"" + dependency + "\": Unable to initialize versioning system " + dependencyResolved.getVersioningSystem().getName(), exception); + break; + } + + // Compare + if (!versionEqual.isEmpty()) { // Version equals + VersioningSystem versioningSystemEquals; + + // Get expected VersioningSystem + try { + versioningSystemEquals = dependencyResolved.getVersioningSystem().getDeclaredConstructor(String.class).newInstance(versionEqual.toString()); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) { + logger.crash("Unable to check version of dependency \"" + dependency + "\": Unable to initialize versioning system " + dependencyResolved.getVersioningSystem().getName(), exception); + break; + } + + // Compare versions + if (versioningSystemResolved.compare(versioningSystemEquals) != 1) + unmetDependencies.add("Dependency \"" + dependency + "\" is not met: Expected version " + versionEqual + " does not match found version " + vector.getVersion()); + } else { + VersioningSystem versioningSystemSmaller = null; + VersioningSystem versioningSystemBigger = null; + + if (!versionSmaller.isEmpty()) + // Get expected VersioningSystem + try { + versioningSystemSmaller = dependencyResolved.getVersioningSystem().getDeclaredConstructor(String.class).newInstance(versionSmaller.toString()); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) { + logger.crash("Unable to check version of dependency \"" + dependency + "\": Unable to initialize versioning system " + dependencyResolved.getVersioningSystem().getName(), exception); + break; + } + if (!versionBigger.isEmpty()) + // Get expected VersioningSystem + try { + versioningSystemBigger = dependencyResolved.getVersioningSystem().getDeclaredConstructor(String.class).newInstance(versionBigger.toString()); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) { + logger.crash("Unable to check version of dependency \"" + dependency + "\": Unable to initialize versioning system " + dependencyResolved.getVersioningSystem().getName(), exception); + break; + } + + // Compare versions + if (versioningSystemSmaller != null && versioningSystemBigger != null) { + if (versioningSystemResolved.compare(versioningSystemSmaller) != 0 && versioningSystemResolved.compare(versioningSystemBigger) != 2) + unmetDependencies.add("Dependency \"" + dependency + "\" is not met: Version " + vector.getVersion() + " is not in range " + versionSmaller + " to " + versionBigger); + } else { + if (versioningSystemSmaller != null) + if (versioningSystemResolved.compare(versioningSystemSmaller) != 0) + unmetDependencies.add("Dependency \"" + dependency + "\" is not met: Version " + vector.getVersion() + " is bigger than " + versionSmaller); + if (versioningSystemBigger != null) + if (versioningSystemResolved.compare(versioningSystemBigger) != 2) + unmetDependencies.add("Dependency \"" + dependency + "\" is not met: Version " + vector.getVersion() + " is smaller than " + versionBigger); + } + } + + /* + if (dependency.contains("=")) { + // Check for '<' and '>' + if (dependency.contains("<") || dependency.contains(">")) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" can't require a specific version and have minimum and maximum version specifiers"); + + // Check for multiple '=' + if (dependency.split("\\\\=").length != 1) + throw new IllegalStateException("The dependency listing \"" + dependency + "\" can't include multiple equals characters"); + + // Get identifier and required version + int index = dependency.indexOf("="); + String identifier = dependency.substring(0, index); + String version = dependency.substring(index + 1); + + DependencyVector dependencyResolved = getMatchingVector(identifier); + if (dependencyResolved == null) + unmetDependencies.add("Dependency \"" + dependency + "\" is not met: Not found"); + else { + VersioningSystem versioningSystemResolved; + VersioningSystem versioningSystemEquals; + + // Create VersioningSystem instances for comparing versions + try { + versioningSystemResolved = dependencyResolved.getVersioningSystem().getDeclaredConstructor(String.class).newInstance(dependencyResolved.getVersion()); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | + InvocationTargetException exception) { + logger.crash("Unable to check version of dependency \"" + dependency + "\": Unable to initialize versioning system " + dependencyResolved.getVersioningSystem().getName(), exception); + break; + } + try { + versioningSystemEquals = dependencyResolved.getVersioningSystem().getDeclaredConstructor(String.class).newInstance(version); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | + InvocationTargetException exception) { + logger.crash("Unable to initialize versioning system " + vector.getVersioningSystem().getName() + " of vector " + vector.getIdentifier(), exception); + break; + } + + // Compare versions + if (versioningSystemResolved.compare(versioningSystemEquals) != 1) + unmetDependencies.add("Dependency \"" + dependency + "\" is not met: Expected version " + version + " does not match found version " + vector.getVersion()); + } + } else { + throw new IllegalStateException("The dependency listing \"" + dependency + "\" does not contain a version identifier"); + } + */ + } + + return unmetDependencies; + } + + /** + * Searches all registered {@link DependencyVector}s for the specified identifier + * and returns the first matching one. + * + * @return matching vector or {@code null} if not found + * @since v1-alpha4 + */ + private @Nullable DependencyVector getMatchingVector(@NotNull String identifier) { + for (DependencyVector vector : vectors) { + if (vector.getIdentifier().equals(identifier)) { + return vector; + } else { + // Search 'provides' for matches + for (String provider : vector.getProvides()) + if (provider.substring(0, provider.indexOf("=")).equals(identifier)) + return vector; + } + } + + return null; + } + /** * Returns the correct order which stuff needs to be loaded/done in. *