package net.minecraftforkage.instsetup.depsort; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class DependencySorter { private DependencySorter() {throw new RuntimeException();} public static <T extends DependencySortedObject> List<T> sort(List<T> inList) throws InvalidInputException, UnfulfilledRequirementException, DependencyCycleException { class ObjectWithInfo { final T object; final String id; final String depString; Collection<ObjectWithInfo> shouldBeAfter = new ArrayList<>(); boolean isSortedYet; int starIndex; ObjectWithInfo(T object) throws InvalidInputException { this.object = object; this.id = this.object.getID(); this.depString = this.object.getDependencies(); if(id == null || id.equals("")) throw new InvalidInputException("IDs cannot be null or blank."); } boolean canGoNow(List<ObjectWithInfo> unsortedObjects) { for(ObjectWithInfo other : shouldBeAfter) if(!other.isSortedYet) return false; for(ObjectWithInfo other : unsortedObjects) if(other.starIndex < starIndex) return false; return true; } @Override public String toString() { return id + "(" + depString + ")"; } } List<ObjectWithInfo> unsortedObjects = new ArrayList<>(); Map<String, ObjectWithInfo> byID = new HashMap<>(); for(T inObject : inList) unsortedObjects.add(new ObjectWithInfo(inObject)); // don't allow mods to rely on accidental ordering Collections.shuffle(unsortedObjects); for(ObjectWithInfo i : unsortedObjects) if(byID.put(i.id, i) != null) throw new InvalidInputException("Duplicate object ID"); // parse dependency strings for(ObjectWithInfo i : unsortedObjects) { if(i.depString.equals("")) continue; for(String dep : i.depString.split(";")) { String[] parts = dep.split(":"); if(parts.length != 2 || parts[1].equals("")) throw new InvalidInputException(i.id+" has invalid dependency string: "+i.depString); if(parts[0].equals("requires")) { if(!byID.containsKey(parts[1])) throw new UnfulfilledRequirementException(i.id+" requires "+parts[1]+" which is not installed"); } else if(parts[0].equals("before")) { int stars = countStars(parts[1]); if(stars != -1) { if(i.starIndex != 0) throw new InvalidInputException(i.id+" has invalid dependency string: "+i.depString); i.starIndex = -stars; } else { ObjectWithInfo other = byID.get(parts[1]); if(other != null) other.shouldBeAfter.add(i); } } else if(parts[0].equals("after")) { int stars = countStars(parts[1]); if(stars != -1) { if(i.starIndex != 0) throw new InvalidInputException(i.id+" has invalid dependency string: "+i.depString); i.starIndex = stars; } else { ObjectWithInfo other = byID.get(parts[1]); if(other != null) i.shouldBeAfter.add(other); } } else throw new InvalidInputException(i.id+" has invalid dependency string: "+i.depString); } } List<T> sortedObjects = new ArrayList<>(); // simple brute-force method; worst case O(N^2 E) if N is the number of objects and E is the number of dependencies // (where after:* counts as a dependency on everything, for example) while(!unsortedObjects.isEmpty()) { Iterator<ObjectWithInfo> it = unsortedObjects.iterator(); boolean movedAny = false; while(it.hasNext()) { ObjectWithInfo item = it.next(); if(item.canGoNow(unsortedObjects)) { it.remove(); sortedObjects.add(item.object); item.isSortedYet = true; movedAny = true; } } if(!movedAny) throw new DependencyCycleException("At least one dependency cycle exists. Unsorted objects: "+unsortedObjects); } return sortedObjects; } private static int countStars(String string) { if(string.length() == 0) return -1; for(int k = 0; k < string.length(); k++) if(string.charAt(k) != '*') return -1; return string.length(); } }