package net.minecraftforge.fml.common; import java.io.File; import java.util.List; import java.util.Map; import java.util.Set; import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer; import net.minecraftforge.fml.common.discovery.ASMDataTable; import net.minecraftforge.fml.common.discovery.ModCandidate; import net.minecraftforge.fml.common.discovery.ModDiscoverer; import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData; import net.minecraftforge.fml.common.functions.ModIdFunction; import net.minecraftforge.fml.common.versioning.ArtifactVersion; import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; import net.minecraftforge.fml.common.versioning.VersionParser; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public class ModAPIManager { public static final ModAPIManager INSTANCE = new ModAPIManager(); private ModAPITransformer transformer; private ASMDataTable dataTable; private Map<String,APIContainer> apiContainers; private static class APIContainer extends DummyModContainer { private List<ArtifactVersion> referredMods; private ArtifactVersion ownerMod; private ArtifactVersion ourVersion; private String providedAPI; private File source; private String version; private Set<String> currentReferents; private Set<String> packages; private boolean selfReferenced; public APIContainer(String providedAPI, String apiVersion, File source, ArtifactVersion ownerMod) { this.providedAPI = providedAPI; this.version = apiVersion; this.ownerMod = ownerMod; this.ourVersion = new DefaultArtifactVersion(providedAPI, apiVersion); this.referredMods = Lists.newArrayList(); this.source = source; this.currentReferents = Sets.newHashSet(); this.packages = Sets.newHashSet(); } @Override public File getSource() { return source; } @Override public String getVersion() { return version; } @Override public String getName() { return "API: "+providedAPI; } @Override public String getModId() { return providedAPI; } @Override public List<ArtifactVersion> getDependants() { return referredMods; } @Override public List<ArtifactVersion> getDependencies() { return selfReferenced ? ImmutableList.<ArtifactVersion>of() : ImmutableList.of(ownerMod); } @Override public ArtifactVersion getProcessedVersion() { return ourVersion; } public void validate(String providedAPI, String apiOwner, String apiVersion) { // TODO Compare this annotation data to the one we first found. Maybe barf if there is inconsistency? } @Override public String toString() { return "APIContainer{"+providedAPI+":"+version+"}"; } public void addAPIReference(String embedded) { if (currentReferents.add(embedded)) { referredMods.add(VersionParser.parseVersionReference(embedded)); } } public void addOwnedPackage(String apiPackage) { packages.add(apiPackage); } public void addAPIReferences(List<String> candidateIds) { for (String modId : candidateIds) { addAPIReference(modId); } } void markSelfReferenced() { selfReferenced = true; } } public void registerDataTableAndParseAPI(ASMDataTable dataTable) { this.dataTable = dataTable; Set<ASMData> apiList = dataTable.getAll("net.minecraftforge.fml.common.API"); apiContainers = Maps.newHashMap(); for (ASMData data : apiList) { Map<String, Object> annotationInfo = data.getAnnotationInfo(); String apiPackage = data.getClassName().substring(0,data.getClassName().indexOf(".package-info")); String providedAPI = (String) annotationInfo.get("provides"); String apiOwner = (String) annotationInfo.get("owner"); String apiVersion = (String) annotationInfo.get("apiVersion"); APIContainer container = apiContainers.get(providedAPI); if (container == null) { container = new APIContainer(providedAPI, apiVersion, data.getCandidate().getModContainer(), VersionParser.parseVersionReference(apiOwner)); apiContainers.put(providedAPI, container); } else { container.validate(providedAPI, apiOwner, apiVersion); } container.addOwnedPackage(apiPackage); for (ModContainer mc : data.getCandidate().getContainedMods()) { String embeddedIn = mc.getModId(); if (container.currentReferents.contains(embeddedIn)) { continue; } FMLLog.fine("Found API %s (owned by %s providing %s) embedded in %s",apiPackage, apiOwner, providedAPI, embeddedIn); if (!embeddedIn.equals(apiOwner)) { container.addAPIReference(embeddedIn); } } } for (APIContainer container : apiContainers.values()) { for (String pkg : container.packages) { Set<ModCandidate> candidates = dataTable.getCandidatesFor(pkg); for (ModCandidate candidate : candidates) { List<String> candidateIds = Lists.transform(candidate.getContainedMods(), new ModIdFunction()); if (!candidateIds.contains(container.ownerMod.getLabel()) && !container.currentReferents.containsAll(candidateIds)) { FMLLog.info("Found mod(s) %s containing declared API package %s (owned by %s) without associated API reference",candidateIds, pkg, container.ownerMod); container.addAPIReferences(candidateIds); } } } if (apiContainers.containsKey(container.ownerMod.getLabel())) { ArtifactVersion owner = container.ownerMod; do { APIContainer parent = apiContainers.get(owner.getLabel()); if (parent == container) { FMLLog.finer("APIContainer %s is it's own parent. skipping", owner); container.markSelfReferenced(); break; } FMLLog.finer("Removing upstream parent %s from %s", parent.ownerMod.getLabel(), container); container.currentReferents.remove(parent.ownerMod.getLabel()); container.referredMods.remove(parent.ownerMod); owner = parent.ownerMod; } while (apiContainers.containsKey(owner.getLabel())); } FMLLog.fine("Creating API container dummy for API %s: owner: %s, dependents: %s", container.providedAPI, container.ownerMod, container.referredMods); } } public void manageAPI(ModClassLoader modClassLoader, ModDiscoverer discoverer) { registerDataTableAndParseAPI(discoverer.getASMTable()); transformer = modClassLoader.addModAPITransformer(dataTable); } public void injectAPIModContainers(List<ModContainer> mods, Map<String, ModContainer> nameLookup) { mods.addAll(apiContainers.values()); nameLookup.putAll(apiContainers); } public void cleanupAPIContainers(List<ModContainer> mods) { mods.removeAll(apiContainers.values()); } public boolean hasAPI(String modId) { return apiContainers.containsKey(modId); } public Iterable<? extends ModContainer> getAPIList() { return apiContainers.values(); } }