diff --git a/fml/LICENSE-fml.txt b/fml/LICENSE-fml.txt index f454481a2..b1345ced9 100644 --- a/fml/LICENSE-fml.txt +++ b/fml/LICENSE-fml.txt @@ -3,6 +3,12 @@ under the GNU LGPL v2.1 or later. Homepage: https://github.com/cpw/FML +This software includes portions from the Apache Maven project at +http://maven.apache.org/ specifically the ComparableVersion.java code. It is +included based on guidelines at +http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html +with notices intact. The only change is a non-functional change of package name. + ======== GNU LESSER GENERAL PUBLIC LICENSE diff --git a/fml/common/cpw/mods/fml/common/DummyModContainer.java b/fml/common/cpw/mods/fml/common/DummyModContainer.java index cedcee3e2..e76d1693b 100644 --- a/fml/common/cpw/mods/fml/common/DummyModContainer.java +++ b/fml/common/cpw/mods/fml/common/DummyModContainer.java @@ -9,16 +9,17 @@ import com.google.common.eventbus.EventBus; import cpw.mods.fml.common.LoaderState.ModState; import cpw.mods.fml.common.discovery.ContainerType; +import cpw.mods.fml.common.versioning.ArtifactVersion; public class DummyModContainer implements ModContainer { private ModMetadata md; - + public DummyModContainer(ModMetadata md) { this.md = md; } - + public DummyModContainer() { } @@ -35,19 +36,19 @@ public class DummyModContainer implements ModContainer } @Override - public List getDependants() + public List getDependants() { return Collections.emptyList(); } @Override - public List getDependencies() + public List getDependencies() { return Collections.emptyList(); } @Override - public List getRequirements() + public List getRequirements() { return Collections.emptyList(); } @@ -110,4 +111,10 @@ public class DummyModContainer implements ModContainer return false; } + @Override + public ArtifactVersion getProcessedVersion() + { + return null; + } + } diff --git a/fml/common/cpw/mods/fml/common/FMLModContainer.java b/fml/common/cpw/mods/fml/common/FMLModContainer.java index 3f574cb13..4de0d8639 100644 --- a/fml/common/cpw/mods/fml/common/FMLModContainer.java +++ b/fml/common/cpw/mods/fml/common/FMLModContainer.java @@ -39,6 +39,8 @@ import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.registry.FMLRegistry; import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; public class FMLModContainer implements ModContainer { @@ -47,16 +49,16 @@ public class FMLModContainer implements ModContainer private File source; private ModMetadata modMetadata; private String className; - private String modId; private Map descriptor; private boolean enabled = true; - private List requirements; - private List dependencies; - private List dependants; + private List requirements; + private List dependencies; + private List dependants; private boolean overridesMetadata; private EventBus eventBus; private LoadController controller; private Multimap, Object> annotations; + private DefaultArtifactVersion processedVersion; public FMLModContainer(String className, File modSource, Map modDescriptor) { @@ -107,9 +109,9 @@ public class FMLModContainer implements ModContainer if (overridesMetadata || !modMetadata.useDependencyInformation) { - List requirements = Lists.newArrayList(); - List dependencies = Lists.newArrayList(); - List dependants = Lists.newArrayList(); + List requirements = Lists.newArrayList(); + List dependencies = Lists.newArrayList(); + List dependants = Lists.newArrayList(); Loader.instance().computeDependencies((String) descriptor.get("dependencies"), requirements, dependencies, dependants); modMetadata.requiredMods = requirements; modMetadata.dependencies = dependencies; @@ -124,19 +126,19 @@ public class FMLModContainer implements ModContainer } @Override - public List getRequirements() + public List getRequirements() { return modMetadata.requiredMods; } @Override - public List getDependencies() + public List getDependencies() { return modMetadata.dependencies; } @Override - public List getDependants() + public List getDependants() { return modMetadata.dependants; } @@ -325,4 +327,14 @@ public class FMLModContainer implements ModContainer Throwables.propagateIfPossible(t); } } + + @Override + public ArtifactVersion getProcessedVersion() + { + if (processedVersion == null) + { + processedVersion = new DefaultArtifactVersion(getModId(), getVersion()); + } + return processedVersion; + } } diff --git a/fml/common/cpw/mods/fml/common/Loader.java b/fml/common/cpw/mods/fml/common/Loader.java index f0b961457..320902869 100644 --- a/fml/common/cpw/mods/fml/common/Loader.java +++ b/fml/common/cpw/mods/fml/common/Loader.java @@ -49,6 +49,8 @@ import cpw.mods.fml.common.functions.ModIdFunction; import cpw.mods.fml.common.toposort.ModSorter; import cpw.mods.fml.common.toposort.ModSortingException; import cpw.mods.fml.common.toposort.TopologicalSort; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; /** * The loader class performs the actual loading of the mod code from disk. @@ -180,12 +182,25 @@ public class Loader FMLLog.fine("Verifying mod requirements are satisfied"); try { + Map modVersions = Maps.newHashMap(); for (ModContainer mod : mods) { - if (!namedMods.keySet().containsAll(mod.getRequirements())) + modVersions.put(mod.getModId(), mod.getProcessedVersion()); + } + + for (ModContainer mod : mods) + { + ImmutableList allDeps = ImmutableList.builder().addAll(mod.getDependants()).addAll(mod.getDependencies()).build(); + for (ArtifactVersion v : allDeps) { - FMLLog.log(Level.SEVERE, "The mod %s (%s) requires mods %s to be available, one or more are not", mod.getModId(), mod.getName(), mod.getRequirements()); - throw new LoaderException(); + if (modVersions.containsKey(v.getLabel())) + { + if (!v.containsVersion(modVersions.get(v.getLabel()))) + { + FMLLog.log(Level.SEVERE, "The mod %s (%s) requires mods %s to be available, one or more are not", mod.getModId(), mod.getName(), allDeps); + throw new LoaderException(); + } + } } } @@ -340,11 +355,11 @@ public class Loader mods = new ArrayList(); namedMods = new HashMap(); state = LoaderState.LOADING; + modController = new LoadController(this); identifyMods(); disableRequestedMods(); sortModList(); mods = ImmutableList.copyOf(mods); - modController = new LoadController(this); // Mod controller state : CONSTRUCTION modController.distributeStateMessage(modClassLoader); modController.transition(LoaderState.PREINITIALIZATION); @@ -448,7 +463,7 @@ public class Loader return modClassLoader; } - public void computeDependencies(String dependencyString, List requirements, List dependencies, List dependants) + public void computeDependencies(String dependencyString, List requirements, List dependencies, List dependants) { if (dependencyString == null || dependencyString.length() == 0) { @@ -460,6 +475,7 @@ public class Loader for (String dep : DEPENDENCYSPLITTER.split(dependencyString)) { List depparts = Lists.newArrayList(DEPENDENCYPARTSPLITTER.split(dep)); + // Need two parts to the string if (depparts.size() != 2) { parseFailure=true; @@ -467,8 +483,14 @@ public class Loader } String instruction = depparts.get(0); String target = depparts.get(1); - boolean targetIsAll = target.equals("*"); - // If we don't have two parts to each substring, this is an invalid dependency string + boolean targetIsAll = target.startsWith("*"); + + // Cannot have an "all" relationship with anything except pure * + if (targetIsAll && target.length()>1) + { + parseFailure = true; + continue; + } // If this is a required element, add it to the required list if ("required-before".equals(instruction) || "required-after".equals(instruction)) @@ -476,7 +498,7 @@ public class Loader // You can't require everything if (!targetIsAll) { - requirements.add(target); + requirements.add(VersionParser.parseVersionReference(target)); } else { @@ -485,15 +507,21 @@ public class Loader } } + // You cannot have a versioned dependency on everything + if (targetIsAll && target.indexOf('@')>-1) + { + parseFailure = true; + continue; + } // before elements are things we are loaded before (so they are our dependants) if ("required-before".equals(instruction) || "before".equals(instruction)) { - dependants.add(target); + dependants.add(VersionParser.parseVersionReference(target)); } // after elements are things that load before we do (so they are out dependencies) else if ("required-after".equals(instruction) || "after".equals(instruction)) { - dependencies.add(target); + dependencies.add(VersionParser.parseVersionReference(target)); } else { diff --git a/fml/common/cpw/mods/fml/common/ModContainer.java b/fml/common/cpw/mods/fml/common/ModContainer.java index 7c7a08264..c7677c71f 100644 --- a/fml/common/cpw/mods/fml/common/ModContainer.java +++ b/fml/common/cpw/mods/fml/common/ModContainer.java @@ -19,6 +19,7 @@ import java.util.List; import com.google.common.eventbus.EventBus; import cpw.mods.fml.common.LoaderState.ModState; +import cpw.mods.fml.common.versioning.ArtifactVersion; /** * The container that wraps around mods in the system. @@ -28,23 +29,23 @@ import cpw.mods.fml.common.LoaderState.ModState; * a mechanism by which we can wrap actual mod code so that the loader and other * facilities can treat mods at arms length. *

- * + * * @author cpw - * + * */ public interface ModContainer { /** * The globally unique modid for this mod - * + * * @return */ String getModId(); /** * A human readable name - * + * * @return */ @@ -52,86 +53,86 @@ public interface ModContainer /** * A human readable version identifier - * + * * @return */ String getVersion(); /** * The location on the file system which this mod came from - * + * * @return */ File getSource(); /** * The metadata for this mod - * + * * @return */ ModMetadata getMetadata(); /** * Attach this mod to it's metadata from the supplied metadata collection - * + * * @param mc */ void bindMetadata(MetadataCollection mc); /** * Set the enabled/disabled state of this mod - * + * * @param enabled */ void setEnabledState(boolean enabled); /** * A list of the modids that this mod requires loaded prior to loading - * + * * @return */ - List getRequirements(); + List getRequirements(); /** * A list of modids that should be loaded prior to this one. The special * value * indicates to load before any other mod. - * + * * @return */ - List getDependencies(); + List getDependencies(); /** * A list of modids that should be loaded after this one. The * special value * indicates to load after any * other mod. - * + * * @return */ - List getDependants(); + List getDependants(); /** * A representative string encapsulating the sorting preferences for this * mod - * + * * @return */ String getSortingRules(); /** * Register the event bus for the mod and the controller for error handling - * Returns if this bus was successfully registered - disabled mods and other + * Returns if this bus was successfully registered - disabled mods and other * mods that don't need real events should return false and avoid further * processing - * + * * @param bus * @param controller - * @return + * @return */ boolean registerBus(EventBus bus, LoadController controller); /** * Does this mod match the supplied mod - * + * * @param mod * @return */ @@ -139,10 +140,12 @@ public interface ModContainer /** * Get the actual mod object - * + * * @return */ Object getMod(); + ArtifactVersion getProcessedVersion(); + ProxyInjector findSidedProxy(); } diff --git a/fml/common/cpw/mods/fml/common/ModContainerFactory.java b/fml/common/cpw/mods/fml/common/ModContainerFactory.java index 74a431c83..dcbb31b19 100644 --- a/fml/common/cpw/mods/fml/common/ModContainerFactory.java +++ b/fml/common/cpw/mods/fml/common/ModContainerFactory.java @@ -24,8 +24,8 @@ public class ModContainerFactory FMLLog.fine("Identified a BaseMod type mod %s", className); return new ModLoaderModContainer(className, modSource, modParser.getBaseModProperties()); } - - for (ModAnnotation ann : modParser.getAnnotations()) + + for (ModAnnotation ann : modParser.getAnnotations()) { if (ann.getASMType().equals(Type.getType(Mod.class))) { @@ -33,7 +33,7 @@ public class ModContainerFactory return new FMLModContainer(className, modSource, ann.getValues()); } } - + return null; } } diff --git a/fml/common/cpw/mods/fml/common/ModMetadata.java b/fml/common/cpw/mods/fml/common/ModMetadata.java index b54eee2dd..220831498 100644 --- a/fml/common/cpw/mods/fml/common/ModMetadata.java +++ b/fml/common/cpw/mods/fml/common/ModMetadata.java @@ -36,12 +36,14 @@ import com.google.common.collect.Maps; import cpw.mods.fml.common.functions.ModNameFunction; import cpw.mods.fml.common.modloader.ModLoaderModContainer; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; import static argo.jdom.JsonNodeBuilders.*; /** * @author cpw - * + * */ public class ModMetadata { @@ -86,9 +88,9 @@ public class ModMetadata public List childMods = Lists.newArrayList(); public boolean useDependencyInformation; - public List requiredMods = Lists.newArrayList(); - public List dependencies = Lists.newArrayList(); - public List dependants = Lists.newArrayList(); + public List requiredMods; + public List dependencies; + public List dependants; public boolean autogenerated; /** @@ -108,9 +110,9 @@ public class ModMetadata credits = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("credits"))); parent = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("parent"))); authorList = Optional.fromNullable((List)processedFields.get(aStringBuilder("authorList"))).or(authorList); - requiredMods = Optional.fromNullable((List)processedFields.get(aStringBuilder("requiredMods"))).or(requiredMods); - dependencies = Optional.fromNullable((List)processedFields.get(aStringBuilder("dependencies"))).or(dependencies); - dependants = Optional.fromNullable((List)processedFields.get(aStringBuilder("dependants"))).or(dependants); + requiredMods = processReferences((List)processedFields.get(aStringBuilder("requiredMods"))); + dependencies = processReferences((List)processedFields.get(aStringBuilder("dependencies"))); + dependants = processReferences((List)processedFields.get(aStringBuilder("dependants"))); useDependencyInformation = Boolean.parseBoolean(Strings.nullToEmpty((String)processedFields.get(aStringBuilder("useDependencyInformation")))); } @@ -118,6 +120,19 @@ public class ModMetadata { } + private List processReferences(List refs) + { + List res = Lists.newArrayList(); + if (refs == null) + { + return res; + } + for (String ref : refs) + { + res.add(VersionParser.parseVersionReference(ref)); + } + return res; + } /** * @return */ diff --git a/fml/common/cpw/mods/fml/common/modloader/ModLoaderModContainer.java b/fml/common/cpw/mods/fml/common/modloader/ModLoaderModContainer.java index 614070f41..487c7a1be 100644 --- a/fml/common/cpw/mods/fml/common/modloader/ModLoaderModContainer.java +++ b/fml/common/cpw/mods/fml/common/modloader/ModLoaderModContainer.java @@ -61,6 +61,8 @@ import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.registry.TickRegistry; import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.ModMetadata; import cpw.mods.fml.common.ProxyInjector; @@ -71,9 +73,9 @@ public class ModLoaderModContainer implements ModContainer private static final ProxyInjector NULLPROXY = new ProxyInjector("","","",null); public BaseMod mod; private File modSource; - public ArrayList requirements = Lists.newArrayList(); - public ArrayList dependencies = Lists.newArrayList(); - public ArrayList dependants = Lists.newArrayList(); + public List requirements = Lists.newArrayList(); + public ArrayList dependencies = Lists.newArrayList(); + public ArrayList dependants = Lists.newArrayList(); private ContainerType sourceType; private ModMetadata metadata; private ProxyInjector sidedProxy; @@ -85,6 +87,7 @@ public class ModLoaderModContainer implements ModContainer private LoadController controller; private boolean enabled = true; private String sortingProperties; + private ArtifactVersion processedVersion; public ModLoaderModContainer(String className, File modSource, String sortingProperties) { @@ -372,19 +375,19 @@ public class ModLoaderModContainer implements ModContainer } @Override - public List getRequirements() + public List getRequirements() { return requirements; } @Override - public List getDependants() + public List getDependants() { return dependants; } @Override - public List getDependencies() + public List getDependencies() { return dependencies; } @@ -545,4 +548,14 @@ public class ModLoaderModContainer implements ModContainer Throwables.propagateIfPossible(t); } } + + @Override + public ArtifactVersion getProcessedVersion() + { + if (processedVersion == null) + { + processedVersion = new DefaultArtifactVersion(modId, getVersion()); + } + return processedVersion; + } } diff --git a/fml/common/cpw/mods/fml/common/toposort/ModSorter.java b/fml/common/cpw/mods/fml/common/toposort/ModSorter.java index 0223fe5d0..5640a8e25 100644 --- a/fml/common/cpw/mods/fml/common/toposort/ModSorter.java +++ b/fml/common/cpw/mods/fml/common/toposort/ModSorter.java @@ -20,6 +20,7 @@ import cpw.mods.fml.common.DummyModContainer; import cpw.mods.fml.common.FMLModContainer; import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.toposort.TopologicalSort.DirectedGraph; +import cpw.mods.fml.common.versioning.ArtifactVersion; /** * @author cpw @@ -60,11 +61,11 @@ public class ModSorter boolean preDepAdded = false; boolean postDepAdded = false; - for (String dep : mod.getDependencies()) + for (ArtifactVersion dep : mod.getDependencies()) { preDepAdded = true; - if (dep.equals("*")) + if (dep.getLabel().equals("*")) { // We are "after" everything modGraph.addEdge(mod, afterAll); @@ -74,17 +75,17 @@ public class ModSorter else { modGraph.addEdge(before, mod); - if (nameLookup.containsKey(dep)) { - modGraph.addEdge(nameLookup.get(dep), mod); + if (nameLookup.containsKey(dep.getLabel())) { + modGraph.addEdge(nameLookup.get(dep.getLabel()), mod); } } } - for (String dep : mod.getDependants()) + for (ArtifactVersion dep : mod.getDependants()) { postDepAdded = true; - if (dep.equals("*")) + if (dep.getLabel().equals("*")) { // We are "before" everything modGraph.addEdge(beforeAll, mod); @@ -94,8 +95,8 @@ public class ModSorter else { modGraph.addEdge(mod, after); - if (nameLookup.containsKey(dep)) { - modGraph.addEdge(mod, nameLookup.get(dep)); + if (nameLookup.containsKey(dep.getLabel())) { + modGraph.addEdge(mod, nameLookup.get(dep.getLabel())); } } } diff --git a/fml/common/cpw/mods/fml/common/versioning/ArtifactVersion.java b/fml/common/cpw/mods/fml/common/versioning/ArtifactVersion.java new file mode 100644 index 000000000..7a574a6a9 --- /dev/null +++ b/fml/common/cpw/mods/fml/common/versioning/ArtifactVersion.java @@ -0,0 +1,34 @@ +package cpw.mods.fml.common.versioning; + +/* + * 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. + */ + +/** + * Describes an artifact version in terms of its components, converts it to/from a string and + * compares two versions. + * + * @author Brett Porter + */ +public interface ArtifactVersion + extends Comparable +{ + String getLabel(); + + boolean containsVersion(ArtifactVersion source); +} diff --git a/fml/common/cpw/mods/fml/common/versioning/ComparableVersion.java b/fml/common/cpw/mods/fml/common/versioning/ComparableVersion.java new file mode 100644 index 000000000..9d4227b1f --- /dev/null +++ b/fml/common/cpw/mods/fml/common/versioning/ComparableVersion.java @@ -0,0 +1,467 @@ +package cpw.mods.fml.common.versioning; + +/* + * 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 java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Locale; +import java.util.Properties; +import java.util.Stack; + +/** + * Generic implementation of version comparison. + * + *

Features: + *

    + *
  • mixing of '-' (dash) and '.' (dot) separators,
  • + *
  • transition between characters and digits also constitutes a separator: + * 1.0alpha1 => [1, 0, alpha, 1]
  • + *
  • unlimited number of version components,
  • + *
  • version components in the text can be digits or strings,
  • + *
  • strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering. + * Well-known qualifiers (case insensitive) are:
      + *
    • snapshot
    • + *
    • alpha or a
    • + *
    • beta or b
    • + *
    • milestone or m
    • + *
    • rc or cr
    • + *
    • (the empty string) or ga or final
    • + *
    • sp
    • + *
    + * Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive), + *
  • + *
  • a dash usually precedes a qualifier, and is always less important than something preceded with a dot.
  • + *

+ * + * @see "Versioning" on Maven Wiki + * @author Kenney Westerhof + * @author Hervé Boutemy + */ +public class ComparableVersion + implements Comparable +{ + private String value; + + private String canonical; + + private ListItem items; + + private interface Item + { + final int INTEGER_ITEM = 0; + final int STRING_ITEM = 1; + final int LIST_ITEM = 2; + + int compareTo( Item item ); + + int getType(); + + boolean isNull(); + } + + /** + * Represents a numeric item in the version item list. + */ + private static class IntegerItem + implements Item + { + private static final BigInteger BigInteger_ZERO = new BigInteger( "0" ); + + private final BigInteger value; + + public static final IntegerItem ZERO = new IntegerItem(); + + private IntegerItem() + { + this.value = BigInteger_ZERO; + } + + public IntegerItem( String str ) + { + this.value = new BigInteger( str ); + } + + public int getType() + { + return INTEGER_ITEM; + } + + public boolean isNull() + { + return BigInteger_ZERO.equals( value ); + } + + public int compareTo( Item item ) + { + if ( item == null ) + { + return BigInteger_ZERO.equals( value ) ? 0 : 1; // 1.0 == 1, 1.1 > 1 + } + + switch ( item.getType() ) + { + case INTEGER_ITEM: + return value.compareTo( ( (IntegerItem) item ).value ); + + case STRING_ITEM: + return 1; // 1.1 > 1-sp + + case LIST_ITEM: + return 1; // 1.1 > 1-1 + + default: + throw new RuntimeException( "invalid item: " + item.getClass() ); + } + } + + public String toString() + { + return value.toString(); + } + } + + /** + * Represents a string in the version item list, usually a qualifier. + */ + private static class StringItem + implements Item + { + private static final String[] QUALIFIERS = { "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" }; + + private static final List _QUALIFIERS = Arrays.asList( QUALIFIERS ); + + private static final Properties ALIASES = new Properties(); + static + { + ALIASES.put( "ga", "" ); + ALIASES.put( "final", "" ); + ALIASES.put( "cr", "rc" ); + } + + /** + * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes + * the version older than one without a qualifier, or more recent. + */ + private static final String RELEASE_VERSION_INDEX = String.valueOf( _QUALIFIERS.indexOf( "" ) ); + + private String value; + + public StringItem( String value, boolean followedByDigit ) + { + if ( followedByDigit && value.length() == 1 ) + { + // a1 = alpha-1, b1 = beta-1, m1 = milestone-1 + switch ( value.charAt( 0 ) ) + { + case 'a': + value = "alpha"; + break; + case 'b': + value = "beta"; + break; + case 'm': + value = "milestone"; + break; + } + } + this.value = ALIASES.getProperty( value , value ); + } + + public int getType() + { + return STRING_ITEM; + } + + public boolean isNull() + { + return ( comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ) == 0 ); + } + + /** + * Returns a comparable value for a qualifier. + * + * This method takes into account the ordering of known qualifiers then unknown qualifiers with lexical ordering. + * + * just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1 + * or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character, + * so this is still fast. If more characters are needed then it requires a lexical sort anyway. + * + * @param qualifier + * @return an equivalent value that can be used with lexical comparison + */ + public static String comparableQualifier( String qualifier ) + { + int i = _QUALIFIERS.indexOf( qualifier ); + + return i == -1 ? ( _QUALIFIERS.size() + "-" + qualifier ) : String.valueOf( i ); + } + + public int compareTo( Item item ) + { + if ( item == null ) + { + // 1-rc < 1, 1-ga > 1 + return comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ); + } + switch ( item.getType() ) + { + case INTEGER_ITEM: + return -1; // 1.any < 1.1 ? + + case STRING_ITEM: + return comparableQualifier( value ).compareTo( comparableQualifier( ( (StringItem) item ).value ) ); + + case LIST_ITEM: + return -1; // 1.any < 1-1 + + default: + throw new RuntimeException( "invalid item: " + item.getClass() ); + } + } + + public String toString() + { + return value; + } + } + + /** + * Represents a version list item. This class is used both for the global item list and for sub-lists (which start + * with '-(number)' in the version specification). + */ + private static class ListItem + extends ArrayList + implements Item + { + public int getType() + { + return LIST_ITEM; + } + + public boolean isNull() + { + return ( size() == 0 ); + } + + void normalize() + { + for( ListIterator iterator = listIterator( size() ); iterator.hasPrevious(); ) + { + Item item = iterator.previous(); + if ( item.isNull() ) + { + iterator.remove(); // remove null trailing items: 0, "", empty list + } + else + { + break; + } + } + } + + public int compareTo( Item item ) + { + if ( item == null ) + { + if ( size() == 0 ) + { + return 0; // 1-0 = 1- (normalize) = 1 + } + Item first = get( 0 ); + return first.compareTo( null ); + } + switch ( item.getType() ) + { + case INTEGER_ITEM: + return -1; // 1-1 < 1.0.x + + case STRING_ITEM: + return 1; // 1-1 > 1-sp + + case LIST_ITEM: + Iterator left = iterator(); + Iterator right = ( (ListItem) item ).iterator(); + + while ( left.hasNext() || right.hasNext() ) + { + Item l = left.hasNext() ? left.next() : null; + Item r = right.hasNext() ? right.next() : null; + + // if this is shorter, then invert the compare and mul with -1 + int result = l == null ? -1 * r.compareTo( l ) : l.compareTo( r ); + + if ( result != 0 ) + { + return result; + } + } + + return 0; + + default: + throw new RuntimeException( "invalid item: " + item.getClass() ); + } + } + + public String toString() + { + StringBuilder buffer = new StringBuilder( "(" ); + for( Iterator iter = iterator(); iter.hasNext(); ) + { + buffer.append( iter.next() ); + if ( iter.hasNext() ) + { + buffer.append( ',' ); + } + } + buffer.append( ')' ); + return buffer.toString(); + } + } + + public ComparableVersion( String version ) + { + parseVersion( version ); + } + + public final void parseVersion( String version ) + { + this.value = version; + + items = new ListItem(); + + version = version.toLowerCase( Locale.ENGLISH ); + + ListItem list = items; + + Stack stack = new Stack(); + stack.push( list ); + + boolean isDigit = false; + + int startIndex = 0; + + for ( int i = 0; i < version.length(); i++ ) + { + char c = version.charAt( i ); + + if ( c == '.' ) + { + if ( i == startIndex ) + { + list.add( IntegerItem.ZERO ); + } + else + { + list.add( parseItem( isDigit, version.substring( startIndex, i ) ) ); + } + startIndex = i + 1; + } + else if ( c == '-' ) + { + if ( i == startIndex ) + { + list.add( IntegerItem.ZERO ); + } + else + { + list.add( parseItem( isDigit, version.substring( startIndex, i ) ) ); + } + startIndex = i + 1; + + if ( isDigit ) + { + list.normalize(); // 1.0-* = 1-* + + if ( ( i + 1 < version.length() ) && Character.isDigit( version.charAt( i + 1 ) ) ) + { + // new ListItem only if previous were digits and new char is a digit, + // ie need to differentiate only 1.1 from 1-1 + list.add( list = new ListItem() ); + + stack.push( list ); + } + } + } + else if ( Character.isDigit( c ) ) + { + if ( !isDigit && i > startIndex ) + { + list.add( new StringItem( version.substring( startIndex, i ), true ) ); + startIndex = i; + } + + isDigit = true; + } + else + { + if ( isDigit && i > startIndex ) + { + list.add( parseItem( true, version.substring( startIndex, i ) ) ); + startIndex = i; + } + + isDigit = false; + } + } + + if ( version.length() > startIndex ) + { + list.add( parseItem( isDigit, version.substring( startIndex ) ) ); + } + + while ( !stack.isEmpty() ) + { + list = (ListItem) stack.pop(); + list.normalize(); + } + + canonical = items.toString(); + } + + private static Item parseItem( boolean isDigit, String buf ) + { + return isDigit ? new IntegerItem( buf ) : new StringItem( buf, false ); + } + + public int compareTo( ComparableVersion o ) + { + return items.compareTo( o.items ); + } + + public String toString() + { + return value; + } + + public boolean equals( Object o ) + { + return ( o instanceof ComparableVersion ) && canonical.equals( ( (ComparableVersion) o ).canonical ); + } + + public int hashCode() + { + return canonical.hashCode(); + } +} diff --git a/fml/common/cpw/mods/fml/common/versioning/DefaultArtifactVersion.java b/fml/common/cpw/mods/fml/common/versioning/DefaultArtifactVersion.java new file mode 100644 index 000000000..6ba93e8f8 --- /dev/null +++ b/fml/common/cpw/mods/fml/common/versioning/DefaultArtifactVersion.java @@ -0,0 +1,78 @@ +package cpw.mods.fml.common.versioning; + +public class DefaultArtifactVersion implements ArtifactVersion +{ + + private ComparableVersion comparableVersion; + private String label; + private boolean unbounded; + private VersionRange range; + + public DefaultArtifactVersion(String versionNumber) + { + comparableVersion = new ComparableVersion(versionNumber); + range = VersionRange.createFromVersion(versionNumber, this); + } + + public DefaultArtifactVersion(String label, VersionRange range) + { + this.label = label; + this.range = range; + } + public DefaultArtifactVersion(String label, String version) + { + this(version); + this.label = label; + } + + public DefaultArtifactVersion(String string, boolean unbounded) + { + this.label = string; + this.unbounded = true; + } + + @Override + public boolean equals(Object obj) + { + return ((DefaultArtifactVersion)obj).containsVersion(this); + } + + @Override + public int compareTo(ArtifactVersion o) + { + return unbounded ? 0 : this.comparableVersion.compareTo(((DefaultArtifactVersion)o).comparableVersion); + } + + @Override + public String getLabel() + { + return label; + } + + @Override + public boolean containsVersion(ArtifactVersion source) + { + if (!source.getLabel().equals(getLabel())) + { + return false; + } + if (unbounded) + { + return true; + } + if (range != null) + { + return range.containsVersion(source); + } + else + { + return false; + } + } + + @Override + public String toString() + { + return label == null ? comparableVersion.toString() : label + ( unbounded ? "" : "@" + range); + } +} diff --git a/fml/common/cpw/mods/fml/common/versioning/InvalidVersionSpecificationException.java b/fml/common/cpw/mods/fml/common/versioning/InvalidVersionSpecificationException.java new file mode 100644 index 000000000..85ffc978b --- /dev/null +++ b/fml/common/cpw/mods/fml/common/versioning/InvalidVersionSpecificationException.java @@ -0,0 +1,34 @@ +package cpw.mods.fml.common.versioning; + +/* + * 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. + */ + +/** + * Occurs when a version is invalid. + * + * @author Brett Porter + */ +public class InvalidVersionSpecificationException + extends Exception +{ + public InvalidVersionSpecificationException( String message ) + { + super( message ); + } +} diff --git a/fml/common/cpw/mods/fml/common/versioning/Restriction.java b/fml/common/cpw/mods/fml/common/versioning/Restriction.java new file mode 100644 index 000000000..9072dec3f --- /dev/null +++ b/fml/common/cpw/mods/fml/common/versioning/Restriction.java @@ -0,0 +1,199 @@ +package cpw.mods.fml.common.versioning; + +/* + * 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. + */ + +/** + * Describes a restriction in versioning. + * + * @author Brett Porter + */ +public class Restriction +{ + private final ArtifactVersion lowerBound; + + private final boolean lowerBoundInclusive; + + private final ArtifactVersion upperBound; + + private final boolean upperBoundInclusive; + + public static final Restriction EVERYTHING = new Restriction( null, false, null, false ); + + public Restriction( ArtifactVersion lowerBound, boolean lowerBoundInclusive, ArtifactVersion upperBound, + boolean upperBoundInclusive ) + { + this.lowerBound = lowerBound; + this.lowerBoundInclusive = lowerBoundInclusive; + this.upperBound = upperBound; + this.upperBoundInclusive = upperBoundInclusive; + } + + public ArtifactVersion getLowerBound() + { + return lowerBound; + } + + public boolean isLowerBoundInclusive() + { + return lowerBoundInclusive; + } + + public ArtifactVersion getUpperBound() + { + return upperBound; + } + + public boolean isUpperBoundInclusive() + { + return upperBoundInclusive; + } + + public boolean containsVersion( ArtifactVersion version ) + { + if ( lowerBound != null ) + { + int comparison = lowerBound.compareTo( version ); + + if ( ( comparison == 0 ) && !lowerBoundInclusive ) + { + return false; + } + if ( comparison > 0 ) + { + return false; + } + } + if ( upperBound != null ) + { + int comparison = upperBound.compareTo( version ); + + if ( ( comparison == 0 ) && !upperBoundInclusive ) + { + return false; + } + if ( comparison < 0 ) + { + return false; + } + } + + return true; + } + + @Override + public int hashCode() + { + int result = 13; + + if ( lowerBound == null ) + { + result += 1; + } + else + { + result += lowerBound.hashCode(); + } + + result *= lowerBoundInclusive ? 1 : 2; + + if ( upperBound == null ) + { + result -= 3; + } + else + { + result -= upperBound.hashCode(); + } + + result *= upperBoundInclusive ? 2 : 3; + + return result; + } + + @Override + public boolean equals( Object other ) + { + if ( this == other ) + { + return true; + } + + if ( !( other instanceof Restriction ) ) + { + return false; + } + + Restriction restriction = (Restriction) other; + if ( lowerBound != null ) + { + if ( !lowerBound.equals( restriction.lowerBound ) ) + { + return false; + } + } + else if ( restriction.lowerBound != null ) + { + return false; + } + + if ( lowerBoundInclusive != restriction.lowerBoundInclusive ) + { + return false; + } + + if ( upperBound != null ) + { + if ( !upperBound.equals( restriction.upperBound ) ) + { + return false; + } + } + else if ( restriction.upperBound != null ) + { + return false; + } + + if ( upperBoundInclusive != restriction.upperBoundInclusive ) + { + return false; + } + + return true; + } + + public String toString() + { + StringBuilder buf = new StringBuilder(); + + buf.append( isLowerBoundInclusive() ? "[" : "(" ); + if ( getLowerBound() != null ) + { + buf.append( getLowerBound().toString() ); + } + buf.append( "," ); + if ( getUpperBound() != null ) + { + buf.append( getUpperBound().toString() ); + } + buf.append( isUpperBoundInclusive() ? "]" : ")" ); + + return buf.toString(); + } +} diff --git a/fml/common/cpw/mods/fml/common/versioning/VersionParser.java b/fml/common/cpw/mods/fml/common/versioning/VersionParser.java new file mode 100644 index 000000000..85e2d1dab --- /dev/null +++ b/fml/common/cpw/mods/fml/common/versioning/VersionParser.java @@ -0,0 +1,64 @@ +package cpw.mods.fml.common.versioning; + +import java.util.List; +import java.util.logging.Level; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.LoaderException; + +/** + * Parses version strings according to the specification here: + * http://docs.codehaus.org/display/MAVEN/Versioning + * and allows for comparison of versions based on that document. + * Bounded version specifications are defined as + * http://maven.apache.org/plugins/maven-enforcer-plugin/rules/versionRanges.html + * + * Borrows heavily from maven version range management code + * + * @author cpw + * + */ +public class VersionParser +{ + private static final Splitter SEPARATOR = Splitter.on('@').omitEmptyStrings().trimResults(); + public static ArtifactVersion parseVersionReference(String labelledRef) + { + if (Strings.isNullOrEmpty(labelledRef)) + { + throw new RuntimeException(String.format("Empty reference %s", labelledRef)); + } + List parts = Lists.newArrayList(SEPARATOR.split(labelledRef)); + if (parts.size()>2) + { + throw new RuntimeException(String.format("Invalid versioned reference %s", labelledRef)); + } + if (parts.size()==1) + { + return new DefaultArtifactVersion(parts.get(0), true); + } + return new DefaultArtifactVersion(parts.get(0),parseRange(parts.get(1))); + } + + public static boolean satisfies(ArtifactVersion target, ArtifactVersion source) + { + return target.containsVersion(source); + } + + public static VersionRange parseRange(String range) + { + try + { + return VersionRange.createFromVersionSpec(range); + } + catch (InvalidVersionSpecificationException e) + { + FMLLog.log(Level.SEVERE, e, "Unable to parse a version range specification successfully %s", range); + throw new LoaderException(e); + } + } +} diff --git a/fml/common/cpw/mods/fml/common/versioning/VersionRange.java b/fml/common/cpw/mods/fml/common/versioning/VersionRange.java new file mode 100644 index 000000000..bd3c18b2e --- /dev/null +++ b/fml/common/cpw/mods/fml/common/versioning/VersionRange.java @@ -0,0 +1,540 @@ +package cpw.mods.fml.common.versioning; +/* + * Modifications by cpw under LGPL 2.1 or later + */ + +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import com.google.common.base.Joiner; + +/** + * Construct a version range from a specification. + * + * @author Brett Porter + */ +public class VersionRange +{ + private final ArtifactVersion recommendedVersion; + + private final List restrictions; + + private VersionRange( ArtifactVersion recommendedVersion, + List restrictions ) + { + this.recommendedVersion = recommendedVersion; + this.restrictions = restrictions; + } + + public ArtifactVersion getRecommendedVersion() + { + return recommendedVersion; + } + + public List getRestrictions() + { + return restrictions; + } + + public VersionRange cloneOf() + { + List copiedRestrictions = null; + + if ( restrictions != null ) + { + copiedRestrictions = new ArrayList(); + + if ( !restrictions.isEmpty() ) + { + copiedRestrictions.addAll( restrictions ); + } + } + + return new VersionRange( recommendedVersion, copiedRestrictions ); + } + + /** + * Create a version range from a string representation + *

+ * Some spec examples are + *

    + *
  • 1.0 Version 1.0
  • + *
  • [1.0,2.0) Versions 1.0 (included) to 2.0 (not included)
  • + *
  • [1.0,2.0] Versions 1.0 to 2.0 (both included)
  • + *
  • [1.5,) Versions 1.5 and higher
  • + *
  • (,1.0],[1.2,) Versions up to 1.0 (included) and 1.2 or higher
  • + *
+ * + * @param spec string representation of a version or version range + * @return a new {@link VersionRange} object that represents the spec + * @throws InvalidVersionSpecificationException + * + */ + public static VersionRange createFromVersionSpec( String spec ) + throws InvalidVersionSpecificationException + { + if ( spec == null ) + { + return null; + } + + List restrictions = new ArrayList(); + String process = spec; + ArtifactVersion version = null; + ArtifactVersion upperBound = null; + ArtifactVersion lowerBound = null; + + while ( process.startsWith( "[" ) || process.startsWith( "(" ) ) + { + int index1 = process.indexOf( ")" ); + int index2 = process.indexOf( "]" ); + + int index = index2; + if ( index2 < 0 || index1 < index2 ) + { + if ( index1 >= 0 ) + { + index = index1; + } + } + + if ( index < 0 ) + { + throw new InvalidVersionSpecificationException( "Unbounded range: " + spec ); + } + + Restriction restriction = parseRestriction( process.substring( 0, index + 1 ) ); + if ( lowerBound == null ) + { + lowerBound = restriction.getLowerBound(); + } + if ( upperBound != null ) + { + if ( restriction.getLowerBound() == null || restriction.getLowerBound().compareTo( upperBound ) < 0 ) + { + throw new InvalidVersionSpecificationException( "Ranges overlap: " + spec ); + } + } + restrictions.add( restriction ); + upperBound = restriction.getUpperBound(); + + process = process.substring( index + 1 ).trim(); + + if ( process.length() > 0 && process.startsWith( "," ) ) + { + process = process.substring( 1 ).trim(); + } + } + + if ( process.length() > 0 ) + { + if ( restrictions.size() > 0 ) + { + throw new InvalidVersionSpecificationException( + "Only fully-qualified sets allowed in multiple set scenario: " + spec ); + } + else + { + version = new DefaultArtifactVersion( process ); + restrictions.add( Restriction.EVERYTHING ); + } + } + + return new VersionRange( version, restrictions ); + } + + private static Restriction parseRestriction( String spec ) + throws InvalidVersionSpecificationException + { + boolean lowerBoundInclusive = spec.startsWith( "[" ); + boolean upperBoundInclusive = spec.endsWith( "]" ); + + String process = spec.substring( 1, spec.length() - 1 ).trim(); + + Restriction restriction; + + int index = process.indexOf( "," ); + + if ( index < 0 ) + { + if ( !lowerBoundInclusive || !upperBoundInclusive ) + { + throw new InvalidVersionSpecificationException( "Single version must be surrounded by []: " + spec ); + } + + ArtifactVersion version = new DefaultArtifactVersion( process ); + + restriction = new Restriction( version, lowerBoundInclusive, version, upperBoundInclusive ); + } + else + { + String lowerBound = process.substring( 0, index ).trim(); + String upperBound = process.substring( index + 1 ).trim(); + if ( lowerBound.equals( upperBound ) ) + { + throw new InvalidVersionSpecificationException( "Range cannot have identical boundaries: " + spec ); + } + + ArtifactVersion lowerVersion = null; + if ( lowerBound.length() > 0 ) + { + lowerVersion = new DefaultArtifactVersion( lowerBound ); + } + ArtifactVersion upperVersion = null; + if ( upperBound.length() > 0 ) + { + upperVersion = new DefaultArtifactVersion( upperBound ); + } + + if ( upperVersion != null && lowerVersion != null && upperVersion.compareTo( lowerVersion ) < 0 ) + { + throw new InvalidVersionSpecificationException( "Range defies version ordering: " + spec ); + } + + restriction = new Restriction( lowerVersion, lowerBoundInclusive, upperVersion, upperBoundInclusive ); + } + + return restriction; + } + + public static VersionRange createFromVersion( String version , ArtifactVersion existing) + { + List restrictions = Collections.emptyList(); + if (existing == null) + { + existing = new DefaultArtifactVersion( version ); + } + return new VersionRange(existing , restrictions ); + } + + /** + * Creates and returns a new VersionRange that is a restriction of this + * version range and the specified version range. + *

+ * Note: Precedence is given to the recommended version from this version range over the + * recommended version from the specified version range. + *

+ * + * @param restriction the VersionRange that will be used to restrict this version + * range. + * @return the VersionRange that is a restriction of this version range and the + * specified version range. + *

+ * The restrictions of the returned version range will be an intersection of the restrictions + * of this version range and the specified version range if both version ranges have + * restrictions. Otherwise, the restrictions on the returned range will be empty. + *

+ *

+ * The recommended version of the returned version range will be the recommended version of + * this version range, provided that ranges falls within the intersected restrictions. If + * the restrictions are empty, this version range's recommended version is used if it is not + * null. If it is null, the specified version range's recommended + * version is used (provided it is non-null). If no recommended version can be + * obtained, the returned version range's recommended version is set to null. + *

+ * @throws NullPointerException if the specified VersionRange is + * null. + */ + public VersionRange restrict( VersionRange restriction ) + { + List r1 = this.restrictions; + List r2 = restriction.restrictions; + List restrictions; + + if ( r1.isEmpty() || r2.isEmpty() ) + { + restrictions = Collections.emptyList(); + } + else + { + restrictions = intersection( r1, r2 ); + } + + ArtifactVersion version = null; + if ( restrictions.size() > 0 ) + { + for ( Restriction r : restrictions ) + { + if ( recommendedVersion != null && r.containsVersion( recommendedVersion ) ) + { + // if we find the original, use that + version = recommendedVersion; + break; + } + else if ( version == null && restriction.getRecommendedVersion() != null + && r.containsVersion( restriction.getRecommendedVersion() ) ) + { + // use this if we can, but prefer the original if possible + version = restriction.getRecommendedVersion(); + } + } + } + // Either the original or the specified version ranges have no restrictions + else if ( recommendedVersion != null ) + { + // Use the original recommended version since it exists + version = recommendedVersion; + } + else if ( restriction.recommendedVersion != null ) + { + // Use the recommended version from the specified VersionRange since there is no + // original recommended version + version = restriction.recommendedVersion; + } +/* TODO: should throw this immediately, but need artifact + else + { + throw new OverConstrainedVersionException( "Restricting incompatible version ranges" ); + } +*/ + + return new VersionRange( version, restrictions ); + } + + private List intersection( List r1, List r2 ) + { + List restrictions = new ArrayList( r1.size() + r2.size() ); + Iterator i1 = r1.iterator(); + Iterator i2 = r2.iterator(); + Restriction res1 = i1.next(); + Restriction res2 = i2.next(); + + boolean done = false; + while ( !done ) + { + if ( res1.getLowerBound() == null || res2.getUpperBound() == null + || res1.getLowerBound().compareTo( res2.getUpperBound() ) <= 0 ) + { + if ( res1.getUpperBound() == null || res2.getLowerBound() == null + || res1.getUpperBound().compareTo( res2.getLowerBound() ) >= 0 ) + { + ArtifactVersion lower; + ArtifactVersion upper; + boolean lowerInclusive; + boolean upperInclusive; + + // overlaps + if ( res1.getLowerBound() == null ) + { + lower = res2.getLowerBound(); + lowerInclusive = res2.isLowerBoundInclusive(); + } + else if ( res2.getLowerBound() == null ) + { + lower = res1.getLowerBound(); + lowerInclusive = res1.isLowerBoundInclusive(); + } + else + { + int comparison = res1.getLowerBound().compareTo( res2.getLowerBound() ); + if ( comparison < 0 ) + { + lower = res2.getLowerBound(); + lowerInclusive = res2.isLowerBoundInclusive(); + } + else if ( comparison == 0 ) + { + lower = res1.getLowerBound(); + lowerInclusive = res1.isLowerBoundInclusive() && res2.isLowerBoundInclusive(); + } + else + { + lower = res1.getLowerBound(); + lowerInclusive = res1.isLowerBoundInclusive(); + } + } + + if ( res1.getUpperBound() == null ) + { + upper = res2.getUpperBound(); + upperInclusive = res2.isUpperBoundInclusive(); + } + else if ( res2.getUpperBound() == null ) + { + upper = res1.getUpperBound(); + upperInclusive = res1.isUpperBoundInclusive(); + } + else + { + int comparison = res1.getUpperBound().compareTo( res2.getUpperBound() ); + if ( comparison < 0 ) + { + upper = res1.getUpperBound(); + upperInclusive = res1.isUpperBoundInclusive(); + } + else if ( comparison == 0 ) + { + upper = res1.getUpperBound(); + upperInclusive = res1.isUpperBoundInclusive() && res2.isUpperBoundInclusive(); + } + else + { + upper = res2.getUpperBound(); + upperInclusive = res2.isUpperBoundInclusive(); + } + } + + // don't add if they are equal and one is not inclusive + if ( lower == null || upper == null || lower.compareTo( upper ) != 0 ) + { + restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) ); + } + else if ( lowerInclusive && upperInclusive ) + { + restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) ); + } + + //noinspection ObjectEquality + if ( upper == res2.getUpperBound() ) + { + // advance res2 + if ( i2.hasNext() ) + { + res2 = i2.next(); + } + else + { + done = true; + } + } + else + { + // advance res1 + if ( i1.hasNext() ) + { + res1 = i1.next(); + } + else + { + done = true; + } + } + } + else + { + // move on to next in r1 + if ( i1.hasNext() ) + { + res1 = i1.next(); + } + else + { + done = true; + } + } + } + else + { + // move on to next in r2 + if ( i2.hasNext() ) + { + res2 = i2.next(); + } + else + { + done = true; + } + } + } + + return restrictions; + } + + public String toString() + { + if ( recommendedVersion != null ) + { + return recommendedVersion.toString(); + } + else + { + return Joiner.on(',').join(restrictions); + } + } + + public ArtifactVersion matchVersion( List versions ) + { + // TODO: could be more efficient by sorting the list and then moving along the restrictions in order? + + ArtifactVersion matched = null; + for ( ArtifactVersion version : versions ) + { + if ( containsVersion( version ) ) + { + // valid - check if it is greater than the currently matched version + if ( matched == null || version.compareTo( matched ) > 0 ) + { + matched = version; + } + } + } + return matched; + } + + public boolean containsVersion( ArtifactVersion version ) + { + for ( Restriction restriction : restrictions ) + { + if ( restriction.containsVersion( version ) ) + { + return true; + } + } + return false; + } + + public boolean hasRestrictions() + { + return !restrictions.isEmpty() && recommendedVersion == null; + } + + public boolean equals( Object obj ) + { + if ( this == obj ) + { + return true; + } + if ( !( obj instanceof VersionRange ) ) + { + return false; + } + VersionRange other = (VersionRange) obj; + + boolean equals = + recommendedVersion == other.recommendedVersion + || ( ( recommendedVersion != null ) && recommendedVersion.equals( other.recommendedVersion ) ); + equals &= + restrictions == other.restrictions + || ( ( restrictions != null ) && restrictions.equals( other.restrictions ) ); + return equals; + } + + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ( recommendedVersion == null ? 0 : recommendedVersion.hashCode() ); + hash = 31 * hash + ( restrictions == null ? 0 : restrictions.hashCode() ); + return hash; + } +} diff --git a/fml/eclipse/simpletestmod/src/net/minecraft/src/mod_testMod.java b/fml/eclipse/simpletestmod/src/net/minecraft/src/mod_testMod.java index 2b2a1b994..9c95159e6 100644 --- a/fml/eclipse/simpletestmod/src/net/minecraft/src/mod_testMod.java +++ b/fml/eclipse/simpletestmod/src/net/minecraft/src/mod_testMod.java @@ -92,10 +92,10 @@ public class mod_testMod extends BaseMod { return interval; } } - + @Override public String getPriorities() { - return "after:MockMod"; + return "after:MockMod@[1.1,1.2),[1.3,)"; } } \ No newline at end of file