2018-06-06 15:37:56 +00:00
/ *
* Minecraft Forge
2020-07-02 17:49:11 +00:00
* Copyright ( c ) 2016 - 2020 .
2018-06-06 15:37:56 +00:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2 . 1
* of the License .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
* /
package net.minecraftforge.fml.loading ;
2019-01-02 00:42:56 +00:00
import com.google.common.graph.GraphBuilder ;
import com.google.common.graph.MutableGraph ;
import net.minecraftforge.forgespi.language.IModFileInfo ;
2019-01-14 03:51:36 +00:00
import net.minecraftforge.forgespi.language.IModInfo ;
2019-01-02 00:42:56 +00:00
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData ;
2018-06-06 15:37:56 +00:00
import net.minecraftforge.fml.loading.moddiscovery.ModFile ;
2018-06-15 19:03:35 +00:00
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo ;
2018-06-06 15:37:56 +00:00
import net.minecraftforge.fml.loading.moddiscovery.ModInfo ;
2019-01-02 00:42:56 +00:00
import net.minecraftforge.fml.loading.toposort.CyclePresentException ;
import net.minecraftforge.fml.loading.toposort.TopologicalSort ;
2019-10-24 01:30:17 +00:00
import net.minecraftforge.forgespi.locating.IModFile ;
2018-06-06 15:37:56 +00:00
import org.apache.logging.log4j.LogManager ;
2018-06-15 19:03:35 +00:00
import org.apache.logging.log4j.Logger ;
2019-01-02 00:42:56 +00:00
import org.apache.logging.log4j.util.StringBuilderFormattable ;
2018-10-04 02:47:07 +00:00
import org.apache.maven.artifact.versioning.ArtifactVersion ;
2018-10-04 04:57:08 +00:00
import org.apache.maven.artifact.versioning.DefaultArtifactVersion ;
2018-06-06 15:37:56 +00:00
2020-11-08 01:47:12 +00:00
import java.util.* ;
2019-01-02 00:42:56 +00:00
import java.util.concurrent.atomic.AtomicInteger ;
2018-06-06 15:37:56 +00:00
import java.util.function.Function ;
import java.util.stream.Collectors ;
2020-11-08 01:47:12 +00:00
import java.util.stream.Stream ;
2018-06-06 15:37:56 +00:00
2018-12-31 21:33:54 +00:00
import static net.minecraftforge.fml.loading.LogMarkers.LOADING ;
2018-06-06 15:37:56 +00:00
public class ModSorter
{
2018-08-27 17:10:07 +00:00
private static final Logger LOGGER = LogManager . getLogger ( ) ;
2018-06-15 19:03:35 +00:00
private List < ModFile > modFiles ;
2018-06-06 15:37:56 +00:00
private List < ModInfo > sortedList ;
private Map < String , ModInfo > modIdNameLookup ;
2020-11-15 16:08:34 +00:00
private List < ModFile > forgeAndMC ;
2018-06-06 15:37:56 +00:00
2018-06-15 19:03:35 +00:00
private ModSorter ( final List < ModFile > modFiles )
2018-06-06 15:37:56 +00:00
{
this . modFiles = modFiles ;
}
2018-06-15 19:03:35 +00:00
public static LoadingModList sort ( List < ModFile > mods )
2018-06-06 15:37:56 +00:00
{
final ModSorter ms = new ModSorter ( mods ) ;
2018-09-29 01:07:46 +00:00
try {
ms . buildUniqueList ( ) ;
2020-11-08 01:47:12 +00:00
} catch ( EarlyLoadingException e ) {
// We cannot build any list with duped mods. We have to abort immediately and report it
2020-11-15 16:08:34 +00:00
return LoadingModList . of ( ms . forgeAndMC , ms . forgeAndMC . stream ( ) . map ( mf - > ( ModInfo ) mf . getModInfos ( ) . get ( 0 ) ) . collect ( Collectors . toList ( ) ) , e ) ;
2020-11-08 01:47:12 +00:00
}
// try and locate languages and validate dependencies
List < EarlyLoadingException . ExceptionData > missingLangs = ms . findLanguages ( ) ;
List < EarlyLoadingException . ExceptionData > missingDeps = ms . verifyDependencyVersions ( ) ;
final List < ExceptionData > failedList = Stream . concat ( missingLangs . stream ( ) , missingDeps . stream ( ) ) . collect ( Collectors . toList ( ) ) ;
// if we miss one or the other, we abort now
if ( ! failedList . isEmpty ( ) ) {
2020-11-15 16:08:34 +00:00
return LoadingModList . of ( ms . forgeAndMC , ms . forgeAndMC . stream ( ) . map ( mf - > ( ModInfo ) mf . getModInfos ( ) . get ( 0 ) ) . collect ( Collectors . toList ( ) ) , new EarlyLoadingException ( " failure to validate mod list " , null , failedList ) ) ;
2020-11-08 01:47:12 +00:00
} else {
// Otherwise, lets try and sort the modlist and proceed
EarlyLoadingException earlyLoadingException = null ;
2020-11-02 19:57:23 +00:00
try {
2020-11-08 01:47:12 +00:00
ms . sort ( ) ;
} catch ( EarlyLoadingException e ) {
earlyLoadingException = e ;
2020-11-02 19:57:23 +00:00
}
2020-11-08 01:47:12 +00:00
return LoadingModList . of ( ms . modFiles , ms . sortedList , earlyLoadingException ) ;
2018-09-29 01:07:46 +00:00
}
2018-06-06 15:37:56 +00:00
}
2020-11-08 01:47:12 +00:00
private List < EarlyLoadingException . ExceptionData > findLanguages ( ) {
List < EarlyLoadingException . ExceptionData > errorData = new ArrayList < > ( ) ;
modFiles . forEach ( modFile - > {
try {
modFile . identifyLanguage ( ) ;
} catch ( EarlyLoadingException e ) {
errorData . addAll ( e . getAllData ( ) ) ;
}
} ) ;
return errorData ;
2018-10-04 15:31:08 +00:00
}
2019-01-26 16:23:51 +00:00
@SuppressWarnings ( " UnstableApiUsage " )
2018-06-15 19:03:35 +00:00
private void sort ( )
{
2019-01-02 00:42:56 +00:00
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
final MutableGraph < ModFileInfo > graph = GraphBuilder . directed ( ) . build ( ) ;
AtomicInteger counter = new AtomicInteger ( ) ;
2019-10-24 01:30:17 +00:00
Map < IModFileInfo , Integer > infos = modFiles
. stream ( )
. map ( ModFile : : getModFileInfo )
. collect ( Collectors . toMap ( Function . identity ( ) , e - > counter . incrementAndGet ( ) ) ) ;
2019-01-24 01:04:57 +00:00
infos . keySet ( ) . forEach ( i - > graph . addNode ( ( ModFileInfo ) i ) ) ;
2019-10-24 01:30:17 +00:00
modFiles
. stream ( )
. map ( ModFile : : getModInfos )
. flatMap ( Collection : : stream )
. map ( IModInfo : : getDependencies )
. flatMap ( Collection : : stream )
. forEach ( dep - > addDependency ( graph , dep ) ) ;
2019-01-02 00:42:56 +00:00
final List < ModFileInfo > sorted ;
2018-06-15 19:03:35 +00:00
try
{
2019-01-02 00:42:56 +00:00
sorted = TopologicalSort . topologicalSort ( graph , Comparator . comparing ( infos : : get ) ) ;
2018-06-15 19:03:35 +00:00
}
2019-01-02 00:42:56 +00:00
catch ( CyclePresentException e )
2018-06-15 19:03:35 +00:00
{
2019-01-02 00:42:56 +00:00
Set < Set < ModFileInfo > > cycles = e . getCycles ( ) ;
LOGGER . error ( LOADING , ( ) - > ( ( StringBuilderFormattable ) ( buffer - > {
buffer . append ( " Mod Sorting failed. \ n " ) ;
buffer . append ( " Detected Cycles: " ) ;
buffer . append ( cycles ) ;
buffer . append ( '\n' ) ;
} ) ) ) ;
List < ExceptionData > dataList = cycles . stream ( )
. map ( Set : : stream )
. map ( stream - > stream
2019-10-24 01:30:17 +00:00
. flatMap ( modFileInfo - > modFileInfo . getMods ( )
. stream ( )
. map ( IModInfo : : getModId ) )
. collect ( Collectors . toList ( ) ) )
2019-01-02 00:42:56 +00:00
. map ( list - > new ExceptionData ( " fml.modloading.cycle " , list ) )
. collect ( Collectors . toList ( ) ) ;
throw new EarlyLoadingException ( " Sorting error " , e , dataList ) ;
2018-06-15 19:03:35 +00:00
}
2019-10-24 01:30:17 +00:00
this . sortedList = sorted
. stream ( )
. map ( ModFileInfo : : getMods )
. flatMap ( Collection : : stream )
. map ( ModInfo . class : : cast )
. collect ( Collectors . toList ( ) ) ;
this . modFiles = sorted
. stream ( )
. map ( ModFileInfo : : getFile )
. collect ( Collectors . toList ( ) ) ;
2018-06-06 15:37:56 +00:00
}
2019-01-26 16:23:51 +00:00
@SuppressWarnings ( " UnstableApiUsage " )
2019-01-02 00:42:56 +00:00
private void addDependency ( MutableGraph < ModFileInfo > topoGraph , IModInfo . ModVersion dep )
2018-06-06 15:37:56 +00:00
{
2019-01-26 22:07:17 +00:00
final ModFileInfo self = ( ModFileInfo ) dep . getOwner ( ) . getOwningFile ( ) ;
final ModInfo targetModInfo = modIdNameLookup . get ( dep . getModId ( ) ) ;
// soft dep that doesn't exist. Just return. No edge required.
if ( targetModInfo = = null ) return ;
final ModFileInfo target = targetModInfo . getOwningFile ( ) ;
2019-01-02 00:42:56 +00:00
if ( self = = target )
return ; // in case a jar has two mods that have dependencies between
2018-06-15 19:03:35 +00:00
switch ( dep . getOrdering ( ) ) {
case BEFORE :
2019-01-02 00:42:56 +00:00
topoGraph . putEdge ( self , target ) ;
2018-06-15 19:03:35 +00:00
break ;
case AFTER :
2019-01-02 00:42:56 +00:00
topoGraph . putEdge ( target , self ) ;
2018-06-15 19:03:35 +00:00
break ;
case NONE :
break ;
}
2018-06-06 15:37:56 +00:00
}
private void buildUniqueList ( )
{
2019-10-24 01:30:17 +00:00
// Collect mod files by first modid in the file. This will be used for deduping purposes
final Map < String , List < IModFile > > modFilesByFirstId = modFiles
. stream ( )
. collect ( Collectors . groupingBy ( mf - > mf . getModInfos ( ) . get ( 0 ) . getModId ( ) ) ) ;
2020-11-15 16:08:34 +00:00
// Capture forge and MC here, so we can keep them for later
forgeAndMC = new ArrayList < > ( ) ;
forgeAndMC . add ( ( ModFile ) modFilesByFirstId . get ( " minecraft " ) . get ( 0 ) ) ;
forgeAndMC . add ( ( ModFile ) modFilesByFirstId . get ( " forge " ) . get ( 0 ) ) ;
2019-10-24 01:30:17 +00:00
// Select the newest by artifact version sorting of non-unique files thus identified
this . modFiles = modFilesByFirstId . entrySet ( ) . stream ( )
. map ( this : : selectNewestModInfo )
. map ( Map . Entry : : getValue )
. map ( ModFile . class : : cast )
. collect ( Collectors . toList ( ) ) ;
// Transform to the full mod id list
final Map < String , List < ModInfo > > modIds = modFiles . stream ( )
2019-09-17 00:14:21 +00:00
. map ( ModFile : : getModInfos )
. flatMap ( Collection : : stream )
2019-10-24 01:30:17 +00:00
. map ( ModInfo . class : : cast )
. collect ( Collectors . groupingBy ( IModInfo : : getModId ) ) ;
2018-06-06 15:37:56 +00:00
2019-10-24 01:30:17 +00:00
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
2019-09-17 00:14:21 +00:00
final List < Map . Entry < String , List < ModInfo > > > dupedMods = modIds
. entrySet ( )
. stream ( )
. filter ( e - > e . getValue ( ) . size ( ) > 1 )
. collect ( Collectors . toList ( ) ) ;
2018-06-06 15:37:56 +00:00
if ( ! dupedMods . isEmpty ( ) ) {
2019-09-17 00:14:21 +00:00
final List < EarlyLoadingException . ExceptionData > duplicateModErrors = dupedMods
. stream ( )
2020-09-23 21:16:36 +00:00
. map ( dm - > new EarlyLoadingException . ExceptionData ( " fml.modloading.dupedmod " , dm . getValue ( ) . get ( 0 ) , Objects . toString ( dm . getValue ( ) . get ( 0 ) ) ) )
2019-09-17 00:14:21 +00:00
. collect ( Collectors . toList ( ) ) ;
2018-10-04 04:57:08 +00:00
throw new EarlyLoadingException ( " Duplicate mods found " , null , duplicateModErrors ) ;
2018-06-06 15:37:56 +00:00
}
2019-09-17 00:14:21 +00:00
modIdNameLookup = modIds
. entrySet ( )
. stream ( )
. collect ( Collectors . toMap ( Map . Entry : : getKey , e - > e . getValue ( ) . get ( 0 ) ) ) ;
2018-06-06 15:37:56 +00:00
}
2019-10-24 01:30:17 +00:00
private Map . Entry < String , IModFile > selectNewestModInfo ( Map . Entry < String , List < IModFile > > fullList ) {
List < IModFile > modInfoList = fullList . getValue ( ) ;
if ( modInfoList . size ( ) > 1 ) {
LOGGER . debug ( " Found {} mods for first modid {}, selecting most recent based on version data " , modInfoList . size ( ) , fullList . getKey ( ) ) ;
modInfoList . sort ( Comparator . < IModFile , ArtifactVersion > comparing ( mf - > mf . getModInfos ( ) . get ( 0 ) . getVersion ( ) ) . reversed ( ) ) ;
LOGGER . debug ( " Selected file {} for modid {} with version {} " , modInfoList . get ( 0 ) . getFileName ( ) , fullList . getKey ( ) , modInfoList . get ( 0 ) . getModInfos ( ) . get ( 0 ) . getVersion ( ) ) ;
}
return new AbstractMap . SimpleImmutableEntry < > ( fullList . getKey ( ) , modInfoList . get ( 0 ) ) ;
}
2020-11-08 01:47:12 +00:00
private List < EarlyLoadingException . ExceptionData > verifyDependencyVersions ( )
2018-06-06 15:37:56 +00:00
{
2019-10-24 01:30:17 +00:00
final Map < String , ArtifactVersion > modVersions = modFiles
. stream ( )
. map ( ModFile : : getModInfos )
. flatMap ( Collection : : stream )
. collect ( Collectors . toMap ( IModInfo : : getModId , IModInfo : : getVersion ) ) ;
final Map < IModInfo , List < IModInfo . ModVersion > > modVersionDependencies = modFiles
. stream ( )
. map ( ModFile : : getModInfos )
. flatMap ( Collection : : stream )
. collect ( Collectors . groupingBy ( Function . identity ( ) , Java9BackportUtils . flatMapping ( e - > e . getDependencies ( ) . stream ( ) , Collectors . toList ( ) ) ) ) ;
final Set < IModInfo . ModVersion > mandatoryModVersions = modVersionDependencies
. values ( )
. stream ( )
. flatMap ( Collection : : stream )
. filter ( mv - > mv . isMandatory ( ) & & mv . getSide ( ) . isCorrectSide ( ) )
. collect ( Collectors . toSet ( ) ) ;
2018-08-27 17:10:07 +00:00
LOGGER . debug ( LOADING , " Found {} mandatory requirements " , mandatoryModVersions . size ( ) ) ;
2019-10-24 01:30:17 +00:00
final Set < IModInfo . ModVersion > missingVersions = mandatoryModVersions
. stream ( )
2020-11-08 01:47:12 +00:00
. filter ( mv - > this . modVersionNotContained ( mv , modVersions ) )
2019-10-24 01:30:17 +00:00
. collect ( Collectors . toSet ( ) ) ;
2018-08-27 17:10:07 +00:00
LOGGER . debug ( LOADING , " Found {} mandatory mod requirements missing " , missingVersions . size ( ) ) ;
2018-06-06 15:37:56 +00:00
if ( ! missingVersions . isEmpty ( ) ) {
2020-11-08 01:47:12 +00:00
return missingVersions
2019-10-24 01:30:17 +00:00
. stream ( )
2020-11-08 01:47:12 +00:00
. map ( mv - > new ExceptionData ( " fml.modloading.missingdependency " , mv . getOwner ( ) ,
2019-10-24 01:30:17 +00:00
mv . getModId ( ) , mv . getOwner ( ) . getModId ( ) , mv . getVersionRange ( ) ,
modVersions . getOrDefault ( mv . getModId ( ) , new DefaultArtifactVersion ( " null " ) ) ) )
. collect ( Collectors . toList ( ) ) ;
2018-06-06 15:37:56 +00:00
}
2020-11-08 01:47:12 +00:00
return Collections . emptyList ( ) ;
2018-06-06 15:37:56 +00:00
}
2020-11-08 01:47:12 +00:00
private boolean modVersionNotContained ( final IModInfo . ModVersion mv , final Map < String , ArtifactVersion > modVersions )
2018-06-06 15:37:56 +00:00
{
2020-11-08 01:47:12 +00:00
return ! ( VersionSupportMatrix . testVersionSupportMatrix ( mv . getVersionRange ( ) , mv . getModId ( ) , " mod " , ( modId , range ) - > modVersions . containsKey ( modId ) & &
( range . containsVersion ( modVersions . get ( modId ) ) | | modVersions . get ( modId ) . toString ( ) . equals ( " NONE " ) ) ) ) ;
2018-06-06 15:37:56 +00:00
}
}