Java tutorial
/* * Minecraft Forge * Copyright (c) 2016-2019. * * 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.common.capabilities; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; import net.minecraftforge.forgespi.language.ModFileScanData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.objectweb.asm.Type; import java.util.function.Function; import java.util.stream.Collectors; import static net.minecraftforge.fml.Logging.CAPABILITIES; public enum CapabilityManager { INSTANCE; private static final Logger LOGGER = LogManager.getLogger(); private static final Type CAP_INJECT = Type.getType(CapabilityInject.class); /** * Registers a capability to be consumed by others. * APIs who define the capability should call this. * To retrieve the Capability instance, use the @CapabilityInject annotation. * This method is safe to call during parallel mod loading. * * @param type The Interface to be registered * @param storage A default implementation of the storage handler. * @param factory A Factory that will produce new instances of the default implementation. */ public <T> void register(Class<T> type, Capability.IStorage<T> storage, Callable<? extends T> factory) { Objects.requireNonNull(type, "Attempted to register a capability with invalid type"); Objects.requireNonNull(storage, "Attempted to register a capability with no storage implementation"); Objects.requireNonNull(factory, "Attempted to register a capability with no default implementation factory"); String realName = type.getName().intern(); Capability<T> cap; synchronized (providers) { if (providers.containsKey(realName)) { LOGGER.error(CAPABILITIES, "Cannot register capability implementation multiple times : {}", realName); throw new IllegalArgumentException( "Cannot register a capability implementation multiple times : " + realName); } cap = new Capability<>(realName, storage, factory); providers.put(realName, cap); } callbacks.getOrDefault(realName, Collections.emptyList()).forEach(func -> func.apply(cap)); } // INTERNAL private final IdentityHashMap<String, Capability<?>> providers = new IdentityHashMap<>(); private volatile IdentityHashMap<String, List<Function<Capability<?>, Object>>> callbacks; public void injectCapabilities(List<ModFileScanData> data) { final List<ModFileScanData.AnnotationData> capabilities = data.stream().map(ModFileScanData::getAnnotations) .flatMap(Collection::stream).filter(a -> CAP_INJECT.equals(a.getAnnotationType())) .collect(Collectors.toList()); final IdentityHashMap<String, List<Function<Capability<?>, Object>>> m = new IdentityHashMap<>(); capabilities.forEach(entry -> attachCapabilityToMethod(m, entry)); callbacks = m; } private static void attachCapabilityToMethod(Map<String, List<Function<Capability<?>, Object>>> cbs, ModFileScanData.AnnotationData entry) { final String targetClass = entry.getClassType().getClassName(); final String targetName = entry.getMemberName(); Type type = (Type) entry.getAnnotationData().get("value"); if (type == null) { LOGGER.warn(CAPABILITIES, "Unable to inject capability at {}.{} (Invalid Annotation)", targetClass, targetName); return; } final String capabilityName = type.getInternalName().replace('/', '.').intern(); List<Function<Capability<?>, Object>> list = cbs.computeIfAbsent(capabilityName, k -> new ArrayList<>()); if (entry.getMemberName().indexOf('(') > 0) { list.add(input -> { try { for (Method mtd : Class.forName(targetClass).getDeclaredMethods()) { if (targetName.equals(mtd.getName() + Type.getMethodDescriptor(mtd))) { if ((mtd.getModifiers() & Modifier.STATIC) != Modifier.STATIC) { LOGGER.warn(CAPABILITIES, "Unable to inject capability {} at {}.{} (Non-Static)", capabilityName, targetClass, targetName); return null; } mtd.setAccessible(true); mtd.invoke(null, input); return null; } } LOGGER.warn(CAPABILITIES, "Unable to inject capability {} at {}.{} (Method Not Found)", capabilityName, targetClass, targetName); } catch (Exception e) { LOGGER.warn(CAPABILITIES, "Unable to inject capability {} at {}.{}", capabilityName, targetClass, targetName, e); } return null; }); } else { list.add(input -> { try { Field field = Class.forName(targetClass).getDeclaredField(targetName); if ((field.getModifiers() & Modifier.STATIC) != Modifier.STATIC) { LOGGER.warn(CAPABILITIES, "Unable to inject capability {} at {}.{} (Non-Static)", capabilityName, targetClass, targetName); return null; } field.setAccessible(true); field.set(null, input); } catch (Exception e) { LOGGER.warn(CAPABILITIES, "Unable to inject capability {} at {}.{}", capabilityName, targetClass, targetName, e); } return null; }); } } }