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