aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc/lll/mixin/LilleroMixinPlugin.java
blob: 8b23e4e5fde9a2fdd13dce19acc89a7d3c5da30c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package ftbsc.lll.mixin;

import ftbsc.lll.IInjector;
import ftbsc.lll.exceptions.InjectionException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;

import java.util.*;

/**
 * Allows you to load your mod's Lillero patches as a Mixin plugin.
 * Extend this class and specify the child as a plugin in your mod's Mixin
 * config. Refer to your mod loader's instructions for details on how this
 * is done.
 * Methods are left non-final in case you want to alter their behaviour in
 * any way, but I can't really see any merit in doing so.
 */
public abstract class LilleroMixinPlugin implements IMixinConfigPlugin {
   /**
    * The {@link Logger} used by this library.
    */
   protected static final Logger LOGGER = LogManager.getLogger(LilleroMixinPlugin.class);

   /**
    * Maps each fully-qualified name to its associated class.
    */
   private final Map<String, List<IInjector>> injectorMap = new HashMap<>();

   /**
    * Whether Lillero should take precedence over regular mixins.
    */
   private final boolean precedence;

   /**
    * The constructor.
    * @param precedence whether Lillero should take precedence over regular mixins
    */
   public LilleroMixinPlugin(boolean precedence) {
      this.precedence = precedence;
   }

   /**
    * Called after the plugin is instantiated, do any setup here.
    * @param mixinPackage The mixin root package from the config
    */
   @Override
   public void onLoad(String mixinPackage) {
      for(IInjector inj : ServiceLoader.load(IInjector.class, this.getClass().getClassLoader())) {
         LOGGER.info("Registering injector {}", inj.name());
         List<IInjector> injectors = this.injectorMap.get(inj.targetClass());
         if(injectors == null) {
            injectors = new ArrayList<>();
            injectorMap.put(inj.targetClass(), injectors);
         }
         injectors.add(inj);
      }
   }

   /**
    * Returns null, so it's effectively ignored.
    * @return always null
    */
   @Override
   public String getRefMapperConfig() {
      return null;
   }

   /**
    * Tells Mixin to always apply these patches.
    * Lillero doesn't support conditional patches: any check should happen
    * within the patch code itself, with the patch code's scope.
    * @param targetClassName fully qualified class name of the target class
    * @param mixinClassName fully qualified class name of the mixin
    * @return always true
    */
   @Override
   public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
      return true;
   }

   /**
    * Does nothing, as we don't need to alter the target class list.
    * @param myTargets target class set from the companion config
    * @param otherTargets target class set incorporating targets from all other
    *                     configs, read-only
    */
   @Override
   public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {}

   /**
    * This does not apply any additional mixins.
    * @return always null
    */
   @Override
   public List<String> getMixins() {
      return null;
   }

   /**
    * Called immediately before a mixin is applied to a target class.
    * Will apply Lillero patches if {@link #precedence} is true.
    * @param className transformed name of the target class
    * @param clazz target class tree
    * @param mixinClassName name of the mixin class
    * @param mixinInfo information about this mixin
    */
   @Override
   public void preApply(String className, ClassNode clazz, String mixinClassName, IMixinInfo mixinInfo) {
      if(precedence) this.applyLilleroPatches(className, clazz);
   }

   /**
    * Called immediately after a mixin is applied to a target class.
    * Will apply Lillero patches if {@link #precedence} is false.
    * @param className transformed name of the target class
    * @param clazz target class tree
    * @param mixinClassName name of the mixin class
    * @param mixinInfo information about this mixin
    */
   @Override
   public void postApply(String className, ClassNode clazz, String mixinClassName, IMixinInfo mixinInfo) {
      if(!precedence) this.applyLilleroPatches(className, clazz);
   }

   /**
    * Applies the appropriate Lillero patches given a node and a class name.
    * @param className the class' fully qualified name
    * @param clazz the target class
    */
   protected void applyLilleroPatches(String className, ClassNode clazz) {
      List<IInjector> injectors = this.injectorMap.remove(className); // remove so it's only once
      if(injectors != null) {
         injectors.forEach((inj) -> clazz.methods.stream()
            .filter(m -> m.name.equals(inj.methodName()) && m.desc.equals(inj.methodDesc()))
            .forEach(m -> {
               try {
                  LOGGER.info(
                     "Patching {}.{} with {} ({})",
                     className, m.name,
                     inj.name(),
                     inj.reason());
                  inj.inject(clazz, m);
               } catch (InjectionException exception) {
                  LOGGER.error("Error applying patch '{}' : {}", inj.name(), exception);
               }
            }));
      }
   }
}