Add in the ability to create versioned dependencies for mod relationships
BaseMod versions are loaded too late so will never work as a versioned dependency requirement, but can have versioned deps of others
This commit is contained in:
parent
d1d12e4ebd
commit
e001103ed8
17 changed files with 1573 additions and 72 deletions
|
@ -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
|
||||
|
|
|
@ -9,6 +9,7 @@ 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
|
||||
{
|
||||
|
@ -35,19 +36,19 @@ public class DummyModContainer implements ModContainer
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDependants()
|
||||
public List<ArtifactVersion> getDependants()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDependencies()
|
||||
public List<ArtifactVersion> getDependencies()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRequirements()
|
||||
public List<ArtifactVersion> getRequirements()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -110,4 +111,10 @@ public class DummyModContainer implements ModContainer
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArtifactVersion getProcessedVersion()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String, Object> descriptor;
|
||||
private boolean enabled = true;
|
||||
private List<String> requirements;
|
||||
private List<String> dependencies;
|
||||
private List<String> dependants;
|
||||
private List<ArtifactVersion> requirements;
|
||||
private List<ArtifactVersion> dependencies;
|
||||
private List<ArtifactVersion> dependants;
|
||||
private boolean overridesMetadata;
|
||||
private EventBus eventBus;
|
||||
private LoadController controller;
|
||||
private Multimap<Class<? extends Annotation>, Object> annotations;
|
||||
private DefaultArtifactVersion processedVersion;
|
||||
|
||||
public FMLModContainer(String className, File modSource, Map<String,Object> modDescriptor)
|
||||
{
|
||||
|
@ -107,9 +109,9 @@ public class FMLModContainer implements ModContainer
|
|||
|
||||
if (overridesMetadata || !modMetadata.useDependencyInformation)
|
||||
{
|
||||
List<String> requirements = Lists.newArrayList();
|
||||
List<String> dependencies = Lists.newArrayList();
|
||||
List<String> dependants = Lists.newArrayList();
|
||||
List<ArtifactVersion> requirements = Lists.newArrayList();
|
||||
List<ArtifactVersion> dependencies = Lists.newArrayList();
|
||||
List<ArtifactVersion> 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<String> getRequirements()
|
||||
public List<ArtifactVersion> getRequirements()
|
||||
{
|
||||
return modMetadata.requiredMods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDependencies()
|
||||
public List<ArtifactVersion> getDependencies()
|
||||
{
|
||||
return modMetadata.dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDependants()
|
||||
public List<ArtifactVersion> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,14 +182,27 @@ public class Loader
|
|||
FMLLog.fine("Verifying mod requirements are satisfied");
|
||||
try
|
||||
{
|
||||
Map<String, ArtifactVersion> modVersions = Maps.newHashMap();
|
||||
for (ModContainer mod : mods)
|
||||
{
|
||||
if (!namedMods.keySet().containsAll(mod.getRequirements()))
|
||||
modVersions.put(mod.getModId(), mod.getProcessedVersion());
|
||||
}
|
||||
|
||||
for (ModContainer mod : mods)
|
||||
{
|
||||
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());
|
||||
ImmutableList<ArtifactVersion> allDeps = ImmutableList.<ArtifactVersion>builder().addAll(mod.getDependants()).addAll(mod.getDependencies()).build();
|
||||
for (ArtifactVersion v : allDeps)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FMLLog.fine("All mod requirements are satisfied");
|
||||
|
||||
|
@ -340,11 +355,11 @@ public class Loader
|
|||
mods = new ArrayList<ModContainer>();
|
||||
namedMods = new HashMap<String, ModContainer>();
|
||||
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<String> requirements, List<String> dependencies, List<String> dependants)
|
||||
public void computeDependencies(String dependencyString, List<ArtifactVersion> requirements, List<ArtifactVersion> dependencies, List<ArtifactVersion> dependants)
|
||||
{
|
||||
if (dependencyString == null || dependencyString.length() == 0)
|
||||
{
|
||||
|
@ -460,6 +475,7 @@ public class Loader
|
|||
for (String dep : DEPENDENCYSPLITTER.split(dependencyString))
|
||||
{
|
||||
List<String> 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
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
@ -90,7 +91,7 @@ public interface ModContainer
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
List<String> getRequirements();
|
||||
List<ArtifactVersion> getRequirements();
|
||||
|
||||
/**
|
||||
* A list of modids that should be loaded prior to this one. The special
|
||||
|
@ -98,7 +99,7 @@ public interface ModContainer
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
List<String> getDependencies();
|
||||
List<ArtifactVersion> getDependencies();
|
||||
|
||||
/**
|
||||
* A list of modids that should be loaded <em>after</em> this one. The
|
||||
|
@ -107,7 +108,7 @@ public interface ModContainer
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
List<String> getDependants();
|
||||
List<ArtifactVersion> getDependants();
|
||||
|
||||
/**
|
||||
* A representative string encapsulating the sorting preferences for this
|
||||
|
@ -144,5 +145,7 @@ public interface ModContainer
|
|||
*/
|
||||
Object getMod();
|
||||
|
||||
ArtifactVersion getProcessedVersion();
|
||||
|
||||
ProxyInjector findSidedProxy();
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ 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.*;
|
||||
|
||||
|
@ -86,9 +88,9 @@ public class ModMetadata
|
|||
public List<ModContainer> childMods = Lists.newArrayList();
|
||||
|
||||
public boolean useDependencyInformation;
|
||||
public List<String> requiredMods = Lists.newArrayList();
|
||||
public List<String> dependencies = Lists.newArrayList();
|
||||
public List<String> dependants = Lists.newArrayList();
|
||||
public List<ArtifactVersion> requiredMods;
|
||||
public List<ArtifactVersion> dependencies;
|
||||
public List<ArtifactVersion> 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<String>)processedFields.get(aStringBuilder("authorList"))).or(authorList);
|
||||
requiredMods = Optional.fromNullable((List<String>)processedFields.get(aStringBuilder("requiredMods"))).or(requiredMods);
|
||||
dependencies = Optional.fromNullable((List<String>)processedFields.get(aStringBuilder("dependencies"))).or(dependencies);
|
||||
dependants = Optional.fromNullable((List<String>)processedFields.get(aStringBuilder("dependants"))).or(dependants);
|
||||
requiredMods = processReferences((List<String>)processedFields.get(aStringBuilder("requiredMods")));
|
||||
dependencies = processReferences((List<String>)processedFields.get(aStringBuilder("dependencies")));
|
||||
dependants = processReferences((List<String>)processedFields.get(aStringBuilder("dependants")));
|
||||
useDependencyInformation = Boolean.parseBoolean(Strings.nullToEmpty((String)processedFields.get(aStringBuilder("useDependencyInformation"))));
|
||||
}
|
||||
|
||||
|
@ -118,6 +120,19 @@ public class ModMetadata
|
|||
{
|
||||
}
|
||||
|
||||
private List<ArtifactVersion> processReferences(List<String> refs)
|
||||
{
|
||||
List<ArtifactVersion> res = Lists.newArrayList();
|
||||
if (refs == null)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
for (String ref : refs)
|
||||
{
|
||||
res.add(VersionParser.parseVersionReference(ref));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
|
|
|
@ -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<String> requirements = Lists.newArrayList();
|
||||
public ArrayList<String> dependencies = Lists.newArrayList();
|
||||
public ArrayList<String> dependants = Lists.newArrayList();
|
||||
public List<ArtifactVersion> requirements = Lists.newArrayList();
|
||||
public ArrayList<ArtifactVersion> dependencies = Lists.newArrayList();
|
||||
public ArrayList<ArtifactVersion> 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<String> getRequirements()
|
||||
public List<ArtifactVersion> getRequirements()
|
||||
{
|
||||
return requirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDependants()
|
||||
public List<ArtifactVersion> getDependants()
|
||||
{
|
||||
return dependants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDependencies()
|
||||
public List<ArtifactVersion> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:brett@apache.org">Brett Porter</a>
|
||||
*/
|
||||
public interface ArtifactVersion
|
||||
extends Comparable<ArtifactVersion>
|
||||
{
|
||||
String getLabel();
|
||||
|
||||
boolean containsVersion(ArtifactVersion source);
|
||||
}
|
467
fml/common/cpw/mods/fml/common/versioning/ComparableVersion.java
Normal file
467
fml/common/cpw/mods/fml/common/versioning/ComparableVersion.java
Normal file
|
@ -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.
|
||||
*
|
||||
* <p>Features:
|
||||
* <ul>
|
||||
* <li>mixing of '<code>-</code>' (dash) and '<code>.</code>' (dot) separators,</li>
|
||||
* <li>transition between characters and digits also constitutes a separator:
|
||||
* <code>1.0alpha1 => [1, 0, alpha, 1]</code></li>
|
||||
* <li>unlimited number of version components,</li>
|
||||
* <li>version components in the text can be digits or strings,</li>
|
||||
* <li>strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering.
|
||||
* Well-known qualifiers (case insensitive) are:<ul>
|
||||
* <li><code>snapshot</code></li>
|
||||
* <li><code>alpha</code> or <code>a</code></li>
|
||||
* <li><code>beta</code> or <code>b</code></li>
|
||||
* <li><code>milestone</code> or <code>m</code></li>
|
||||
* <li><code>rc</code> or <code>cr</code></li>
|
||||
* <li><code>(the empty string)</code> or <code>ga</code> or <code>final</code></li>
|
||||
* <li><code>sp</code></li>
|
||||
* </ul>
|
||||
* Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive),
|
||||
* </li>
|
||||
* <li>a dash usually precedes a qualifier, and is always less important than something preceded with a dot.</li>
|
||||
* </ul></p>
|
||||
*
|
||||
* @see <a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki</a>
|
||||
* @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
|
||||
* @author <a href="mailto:hboutemy@apache.org">Hervé Boutemy</a>
|
||||
*/
|
||||
public class ComparableVersion
|
||||
implements Comparable<ComparableVersion>
|
||||
{
|
||||
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<String> _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<Item>
|
||||
implements Item
|
||||
{
|
||||
public int getType()
|
||||
{
|
||||
return LIST_ITEM;
|
||||
}
|
||||
|
||||
public boolean isNull()
|
||||
{
|
||||
return ( size() == 0 );
|
||||
}
|
||||
|
||||
void normalize()
|
||||
{
|
||||
for( ListIterator<Item> 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<Item> left = iterator();
|
||||
Iterator<Item> 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<Item> 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<Item> stack = new Stack<Item>();
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:brett@apache.org">Brett Porter</a>
|
||||
*/
|
||||
public class InvalidVersionSpecificationException
|
||||
extends Exception
|
||||
{
|
||||
public InvalidVersionSpecificationException( String message )
|
||||
{
|
||||
super( message );
|
||||
}
|
||||
}
|
199
fml/common/cpw/mods/fml/common/versioning/Restriction.java
Normal file
199
fml/common/cpw/mods/fml/common/versioning/Restriction.java
Normal file
|
@ -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 <a href="mailto:brett@apache.org">Brett Porter</a>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
64
fml/common/cpw/mods/fml/common/versioning/VersionParser.java
Normal file
64
fml/common/cpw/mods/fml/common/versioning/VersionParser.java
Normal file
|
@ -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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
540
fml/common/cpw/mods/fml/common/versioning/VersionRange.java
Normal file
540
fml/common/cpw/mods/fml/common/versioning/VersionRange.java
Normal file
|
@ -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 <a href="mailto:brett@apache.org">Brett Porter</a>
|
||||
*/
|
||||
public class VersionRange
|
||||
{
|
||||
private final ArtifactVersion recommendedVersion;
|
||||
|
||||
private final List<Restriction> restrictions;
|
||||
|
||||
private VersionRange( ArtifactVersion recommendedVersion,
|
||||
List<Restriction> restrictions )
|
||||
{
|
||||
this.recommendedVersion = recommendedVersion;
|
||||
this.restrictions = restrictions;
|
||||
}
|
||||
|
||||
public ArtifactVersion getRecommendedVersion()
|
||||
{
|
||||
return recommendedVersion;
|
||||
}
|
||||
|
||||
public List<Restriction> getRestrictions()
|
||||
{
|
||||
return restrictions;
|
||||
}
|
||||
|
||||
public VersionRange cloneOf()
|
||||
{
|
||||
List<Restriction> copiedRestrictions = null;
|
||||
|
||||
if ( restrictions != null )
|
||||
{
|
||||
copiedRestrictions = new ArrayList<Restriction>();
|
||||
|
||||
if ( !restrictions.isEmpty() )
|
||||
{
|
||||
copiedRestrictions.addAll( restrictions );
|
||||
}
|
||||
}
|
||||
|
||||
return new VersionRange( recommendedVersion, copiedRestrictions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a version range from a string representation
|
||||
* <p/>
|
||||
* Some spec examples are
|
||||
* <ul>
|
||||
* <li><code>1.0</code> Version 1.0</li>
|
||||
* <li><code>[1.0,2.0)</code> Versions 1.0 (included) to 2.0 (not included)</li>
|
||||
* <li><code>[1.0,2.0]</code> Versions 1.0 to 2.0 (both included)</li>
|
||||
* <li><code>[1.5,)</code> Versions 1.5 and higher</li>
|
||||
* <li><code>(,1.0],[1.2,)</code> Versions up to 1.0 (included) and 1.2 or higher</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<Restriction> restrictions = new ArrayList<Restriction>();
|
||||
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<Restriction> restrictions = Collections.emptyList();
|
||||
if (existing == null)
|
||||
{
|
||||
existing = new DefaultArtifactVersion( version );
|
||||
}
|
||||
return new VersionRange(existing , restrictions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a new <code>VersionRange</code> that is a restriction of this
|
||||
* version range and the specified version range.
|
||||
* <p>
|
||||
* Note: Precedence is given to the recommended version from this version range over the
|
||||
* recommended version from the specified version range.
|
||||
* </p>
|
||||
*
|
||||
* @param restriction the <code>VersionRange</code> that will be used to restrict this version
|
||||
* range.
|
||||
* @return the <code>VersionRange</code> that is a restriction of this version range and the
|
||||
* specified version range.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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
|
||||
* <code>null</code>. If it is <code>null</code>, the specified version range's recommended
|
||||
* version is used (provided it is non-<code>null</code>). If no recommended version can be
|
||||
* obtained, the returned version range's recommended version is set to <code>null</code>.
|
||||
* </p>
|
||||
* @throws NullPointerException if the specified <code>VersionRange</code> is
|
||||
* <code>null</code>.
|
||||
*/
|
||||
public VersionRange restrict( VersionRange restriction )
|
||||
{
|
||||
List<Restriction> r1 = this.restrictions;
|
||||
List<Restriction> r2 = restriction.restrictions;
|
||||
List<Restriction> 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<Restriction> intersection( List<Restriction> r1, List<Restriction> r2 )
|
||||
{
|
||||
List<Restriction> restrictions = new ArrayList<Restriction>( r1.size() + r2.size() );
|
||||
Iterator<Restriction> i1 = r1.iterator();
|
||||
Iterator<Restriction> 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<ArtifactVersion> 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;
|
||||
}
|
||||
}
|
|
@ -96,6 +96,6 @@ public class mod_testMod extends BaseMod {
|
|||
@Override
|
||||
public String getPriorities()
|
||||
{
|
||||
return "after:MockMod";
|
||||
return "after:MockMod@[1.1,1.2),[1.3,)";
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue