2016-06-22 23:49:47 -04:00
/*
2022-04-14 03:42:00 +00:00
* Copyright (c) Forge Development LLC and contributors
2022-02-28 13:41:23 -08:00
* SPDX-License-Identifier: LGPL-2.1-only
2016-06-22 23:49:47 -04:00
*/
2017-06-19 15:02:18 -07:00
package net.minecraftforge.registries ;
2014-05-24 22:32:24 -04:00
2019-01-09 20:57:01 -08:00
import java.lang.annotation.ElementType ;
2014-05-24 22:32:24 -04:00
import java.lang.reflect.Field ;
2019-01-09 20:57:01 -08:00
import java.util.Collection ;
import java.util.HashSet ;
2014-05-24 22:32:24 -04:00
import java.util.List ;
2019-01-09 20:57:01 -08:00
import java.util.Locale ;
2014-05-24 22:32:24 -04:00
import java.util.Map ;
import java.util.Set ;
2019-01-16 12:28:13 -08:00
import java.util.function.Consumer ;
import java.util.function.Predicate ;
2014-09-22 22:01:24 -07:00
2021-07-17 00:08:49 -05:00
import net.minecraft.resources.ResourceLocation ;
2019-01-09 20:57:01 -08:00
import net.minecraftforge.fml.ModList ;
2017-06-20 18:39:55 -07:00
import net.minecraftforge.fml.common.Mod ;
2019-01-13 22:51:36 -05:00
import net.minecraftforge.forgespi.language.ModFileScanData ;
2014-09-22 22:01:24 -07:00
2014-05-24 22:32:24 -04:00
import com.google.common.collect.Maps ;
2018-09-06 04:39:01 -07:00
import org.apache.logging.log4j.LogManager ;
import org.apache.logging.log4j.Logger ;
2022-06-07 15:31:26 -05:00
import org.jetbrains.annotations.Nullable ;
2018-09-06 04:39:01 -07:00
import org.objectweb.asm.Opcodes ;
2019-01-09 20:57:01 -08:00
import org.objectweb.asm.Type ;
2018-09-06 04:39:01 -07:00
2014-05-24 22:32:24 -04:00
/**
* Internal registry for tracking {@link ObjectHolder} references
*/
2019-01-09 20:57:01 -08:00
public class ObjectHolderRegistry
2017-06-19 15:02:18 -07:00
{
2019-01-09 20:57:01 -08:00
/**
* Exposed to allow modders to register their own notification handlers.
* This runnable will be called after a registry snapshot has been injected and finalized.
* The internal list is backed by a HashSet so it is HIGHLY recommended you implement a proper equals
* and hashCode function to de-duplicate callers here.
* The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on.
*/
2022-04-01 15:49:46 -05:00
public static synchronized void addHandler ( Consumer < Predicate < ResourceLocation >> ref )
2019-01-09 20:57:01 -08:00
{
objectHolders . add ( ref );
}
/**
* Removed the specified handler from the notification list.
*
* The internal list is backed by a hash set, and so proper hashCode and equals operations are required for success.
*
* The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on.
*
* @return true if handler was matched and removed.
*/
2022-04-01 15:49:46 -05:00
public static synchronized boolean removeHandler ( Consumer < Predicate < ResourceLocation >> ref )
2019-01-09 20:57:01 -08:00
{
return objectHolders . remove ( ref );
}
//==============================================================
// Everything below is internal, do not use.
//==============================================================
2018-09-06 04:39:01 -07:00
private static final Logger LOGGER = LogManager . getLogger ();
2019-01-16 12:28:13 -08:00
private static final Set < Consumer < Predicate < ResourceLocation >>> objectHolders = new HashSet <> ();
2019-01-09 20:57:01 -08:00
private static final Type OBJECT_HOLDER = Type . getType ( ObjectHolder . class );
private static final Type MOD = Type . getType ( Mod . class );
2022-06-07 15:31:26 -05:00
// Hardcoded list of vanilla classes that should have object holders for each field of the given registry type.
// IMPORTANT: Updates to this collection must be reflected in ObjectHolderDefinalize. Duplicated cuz classloaders, yay!
// Classnames are validated below.
private static final List < VanillaObjectHolderData > VANILLA_OBJECT_HOLDERS = List . of (
new VanillaObjectHolderData ( "net.minecraft.world.level.block.Blocks" , "block" , "net.minecraft.world.level.block.Block" ),
new VanillaObjectHolderData ( "net.minecraft.world.item.Items" , "item" , "net.minecraft.world.item.Item" ),
new VanillaObjectHolderData ( "net.minecraft.world.item.enchantment.Enchantments" , "enchantment" , "net.minecraft.world.item.enchantment.Enchantment" ),
new VanillaObjectHolderData ( "net.minecraft.world.effect.MobEffects" , "mob_effect" , "net.minecraft.world.effect.MobEffect" ),
new VanillaObjectHolderData ( "net.minecraft.core.particles.ParticleTypes" , "particle_type" , "net.minecraft.core.particles.ParticleType" ),
new VanillaObjectHolderData ( "net.minecraft.sounds.SoundEvents" , "sound_event" , "net.minecraft.sounds.SoundEvent" )
);
2019-01-09 20:57:01 -08:00
public static void findObjectHolders ()
2014-05-24 22:32:24 -04:00
{
2023-07-17 17:26:28 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Processing ObjectHolder annotations" );
2019-01-09 20:57:01 -08:00
final List < ModFileScanData . AnnotationData > annotations = ModList . get (). getAllScanData (). stream ()
. map ( ModFileScanData :: getAnnotations )
. flatMap ( Collection :: stream )
2021-07-17 00:08:49 -05:00
. filter ( a -> OBJECT_HOLDER . equals ( a . annotationType ()) || MOD . equals ( a . annotationType ()))
2022-06-07 15:31:26 -05:00
. toList ();
2018-09-06 04:39:01 -07:00
2019-01-09 20:57:01 -08:00
Map < Type , String > classModIds = Maps . newHashMap ();
Map < Type , Class <?>> classCache = Maps . newHashMap ();
2022-06-07 15:31:26 -05:00
// Gather all @Mod classes so that @ObjectHolder's in those classes don't need to specify the mod id; modder convenience
annotations . stream ()
. filter ( a -> MOD . equals ( a . annotationType ()))
. forEach ( data -> classModIds . put ( data . clazz (), ( String ) data . annotationData (). get ( "value" )));
// Validate all the vanilla class-level object holders then scan those first
VANILLA_OBJECT_HOLDERS . forEach ( data -> {
try
{
Class <?> holderClass = Class . forName ( data . holderClass (), true , ObjectHolderRegistry . class . getClassLoader ());
Class <?> registryClass = Class . forName ( data . registryType (), true , ObjectHolderRegistry . class . getClassLoader ());
Type holderType = Type . getType ( holderClass );
classCache . put ( holderType , holderClass );
scanTarget ( classModIds , classCache , holderType , null , registryClass , data . registryName (), "minecraft" , true , true );
}
catch ( ClassNotFoundException e )
{
throw new RuntimeException ( "Vanilla class not found, should not be possible" , e );
}
});
2018-09-06 04:39:01 -07:00
2022-06-07 15:31:26 -05:00
// Scan actual fields annotated with @ObjectHolder second
annotations . stream ()
. filter ( a -> OBJECT_HOLDER . equals ( a . annotationType ())). filter ( a -> a . targetType () == ElementType . FIELD )
. forEach ( data -> scanTarget ( classModIds , classCache , data . clazz (),
data . memberName (), null , ( String ) data . annotationData (). get ( "registryName" ),
( String ) data . annotationData (). get ( "value" ), false , false ));
2019-01-09 20:57:01 -08:00
2023-07-17 17:26:28 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Found {} ObjectHolder annotations" , objectHolders . size ());
2014-05-24 22:32:24 -04:00
}
2019-01-09 20:57:01 -08:00
2022-06-07 15:31:26 -05:00
private static void scanTarget ( Map < Type , String > classModIds , Map < Type , Class <?>> classCache , Type type ,
@Nullable String annotationTarget , @Nullable Class <?> registryClass , String registryName ,
String value , boolean isClass , boolean extractFromValue )
2014-05-24 22:32:24 -04:00
{
Class <?> clazz ;
2019-01-09 20:57:01 -08:00
if ( classCache . containsKey ( type ))
2014-05-24 22:32:24 -04:00
{
2019-01-09 20:57:01 -08:00
clazz = classCache . get ( type );
2014-05-24 22:32:24 -04:00
}
else
{
try
{
2019-01-09 20:57:01 -08:00
clazz = Class . forName ( type . getClassName (), extractFromValue , ObjectHolderRegistry . class . getClassLoader ());
classCache . put ( type , clazz );
2014-05-24 22:32:24 -04:00
}
2017-07-13 05:50:28 +01:00
catch ( ClassNotFoundException ex )
2014-05-24 22:32:24 -04:00
{
// unpossible?
2017-06-19 15:02:18 -07:00
throw new RuntimeException ( ex );
2014-05-24 22:32:24 -04:00
}
}
if ( isClass )
{
2022-06-07 15:31:26 -05:00
scanClassForFields ( classModIds , type , new ResourceLocation ( registryName ), registryClass , value , clazz , extractFromValue );
2014-05-24 22:32:24 -04:00
}
else
{
if ( value . indexOf ( ':' ) == - 1 )
{
2019-01-09 20:57:01 -08:00
String prefix = classModIds . get ( type );
2014-05-24 22:32:24 -04:00
if ( prefix == null )
{
2023-07-17 17:26:28 -07:00
LOGGER . warn ( ForgeRegistry . REGISTRIES , "Found an unqualified ObjectHolder annotation ({}) without a modid context at {}.{}, ignoring" , value , type , annotationTarget );
2014-05-24 22:32:24 -04:00
throw new IllegalStateException ( "Unqualified reference to ObjectHolder" );
}
2019-01-09 20:57:01 -08:00
value = prefix + ':' + value ;
2014-05-24 22:32:24 -04:00
}
try
{
2017-06-20 18:39:55 -07:00
Field f = clazz . getDeclaredField ( annotationTarget );
2022-06-07 15:31:26 -05:00
ObjectHolderRef ref = ObjectHolderRef . create ( new ResourceLocation ( registryName ), f , value , extractFromValue );
if ( ref != null )
2019-01-09 20:57:01 -08:00
addHandler ( ref );
2014-05-24 22:32:24 -04:00
}
2017-07-13 05:50:28 +01:00
catch ( NoSuchFieldException ex )
2014-05-24 22:32:24 -04:00
{
// unpossible?
2017-06-19 15:02:18 -07:00
throw new RuntimeException ( ex );
2014-05-24 22:32:24 -04:00
}
}
}
2022-06-07 15:31:26 -05:00
private static void scanClassForFields ( Map < Type , String > classModIds , Type targetClass ,
ResourceLocation registryName , Class <?> registryClass , String value , Class <?> clazz , boolean extractFromExistingValues )
2014-05-24 22:32:24 -04:00
{
2019-01-09 20:57:01 -08:00
classModIds . put ( targetClass , value );
2018-09-06 04:39:01 -07:00
final int flags = Opcodes . ACC_PUBLIC | Opcodes . ACC_STATIC | Opcodes . ACC_SYNTHETIC ;
2014-05-24 22:32:24 -04:00
for ( Field f : clazz . getFields ())
{
2022-06-07 15:31:26 -05:00
if ((( f . getModifiers () & flags ) != flags ) || f . isAnnotationPresent ( ObjectHolder . class ) || ! registryClass . isAssignableFrom ( f . getType ()))
2014-05-24 22:32:24 -04:00
continue ;
2022-06-07 15:31:26 -05:00
ObjectHolderRef ref = ObjectHolderRef . create ( registryName , f , value + ':' + f . getName (). toLowerCase ( Locale . ENGLISH ), extractFromExistingValues );
if ( ref != null )
2019-01-09 20:57:01 -08:00
addHandler ( ref );
2014-05-26 10:58:13 -04:00
}
2014-05-24 22:32:24 -04:00
}
2022-06-07 15:31:26 -05:00
private static ResourceLocation getRegistryName ( Map < Type , ResourceLocation > classRegistryNames , @Nullable String registryName ,
Type targetClass , Object declaration )
{
if ( registryName != null )
return new ResourceLocation ( registryName );
if ( classRegistryNames . containsKey ( targetClass ))
return classRegistryNames . get ( targetClass );
throw new IllegalStateException ( "No registry name was declared for " + declaration );
}
2019-01-09 20:57:01 -08:00
public static void applyObjectHolders ()
2014-05-24 22:32:24 -04:00
{
2022-06-07 15:31:26 -05:00
try
{
2023-07-17 17:26:28 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Applying holder lookups" );
2022-06-07 15:31:26 -05:00
applyObjectHolders ( key -> true );
2023-07-17 17:26:28 -07:00
LOGGER . debug ( ForgeRegistry . REGISTRIES , "Holder lookups applied" );
2022-06-07 15:31:26 -05:00
} catch ( RuntimeException e )
{
// It is more important that the calling contexts continue without exception to prevent further cascading errors
LOGGER . error ( "" , e );
}
2014-05-24 22:32:24 -04:00
}
2019-01-16 12:28:13 -08:00
public static void applyObjectHolders ( Predicate < ResourceLocation > filter )
{
2022-04-01 15:49:46 -05:00
RuntimeException aggregate = new RuntimeException ( "Failed to apply some object holders, see suppressed exceptions for details" );
objectHolders . forEach ( objectHolder -> {
try
{
objectHolder . accept ( filter );
}
catch ( Exception e )
{
aggregate . addSuppressed ( e );
}
});
if ( aggregate . getSuppressed (). length > 0 )
{
throw aggregate ;
}
2019-01-16 12:28:13 -08:00
}
2022-06-07 15:31:26 -05:00
private record VanillaObjectHolderData ( String holderClass , String registryName , String registryType ) {}
2014-05-24 22:32:24 -04:00
}