/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.tools.obfuscation; import java.util.List; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.tools.Diagnostic.Kind; import org.spongepowered.asm.mixin.Interface.Remap; import org.spongepowered.asm.obfuscation.mapping.common.MappingMethod; import org.spongepowered.tools.obfuscation.interfaces.IMixinAnnotationProcessor; import org.spongepowered.tools.obfuscation.mirror.AnnotationHandle; import org.spongepowered.tools.obfuscation.mirror.MethodHandle; import org.spongepowered.tools.obfuscation.mirror.TypeUtils; import org.spongepowered.tools.obfuscation.mirror.TypeHandle; /** * A module for {@link AnnotatedMixin} whic handles soft-implements clauses */ public class AnnotatedMixinElementHandlerSoftImplements extends AnnotatedMixinElementHandler { AnnotatedMixinElementHandlerSoftImplements(IMixinAnnotationProcessor ap, AnnotatedMixin mixin) { super(ap, mixin); } /** * Process a soft-implements annotation on a mixin. This causes the * interface declared in the annotation and all of its super-interfaces to * be enumerated for member methods. Any member methods which are discovered * in the mixin are then tested for remapability based on the strategy * defined in the soft-implements decoration * * @param implementsAnnotation the @Implements annotation on the * element */ public void process(AnnotationHandle implementsAnnotation) { if (!this.mixin.remap()) { return; } List<AnnotationHandle> interfaces = implementsAnnotation.getAnnotationList("value"); // Derp? if (interfaces.size() < 1) { this.ap.printMessage(Kind.WARNING, "Empty @Implements annotation", this.mixin.getMixin(), implementsAnnotation.asMirror()); return; } for (AnnotationHandle interfaceAnnotation : interfaces) { Remap remap = interfaceAnnotation.<Remap>getValue("remap", Remap.ALL); if (remap == Remap.NONE) { continue; } try { TypeHandle iface = new TypeHandle(interfaceAnnotation.<DeclaredType>getValue("iface")); String prefix = interfaceAnnotation.<String>getValue("prefix"); this.processSoftImplements(remap, iface, prefix); } catch (Exception ex) { this.ap.printMessage(Kind.ERROR, "Unexpected error: " + ex.getClass().getName() + ": " + ex.getMessage(), this.mixin.getMixin(), interfaceAnnotation.asMirror()); } } } /** * Recursive function which processes methods in an interface and its parent * interfaces and adds mappings as necessary * * @param remap Remapping strategy to use * @param iface Interface to enumerate * @param prefix Prefix declared in the soft-implements decoration */ private void processSoftImplements(Remap remap, TypeHandle iface, String prefix) { for (ExecutableElement method : iface.<ExecutableElement>getEnclosedElements(ElementKind.METHOD)) { this.processMethod(remap, iface, prefix, method); } for (TypeHandle superInterface : iface.getInterfaces()) { this.processSoftImplements(remap, superInterface, prefix); } } /** * Process an interface method. Searches for the interface method with the * declared prefix and also searches without prefix if the <tt>ALL</tt> * strategy is selected * * @param remap Remapping strategy to use * @param iface Interface to enumerate * @param prefix Prefix declared in the soft-implements decoration * @param method Interface method to search for */ private void processMethod(Remap remap, TypeHandle iface, String prefix, ExecutableElement method) { String name = method.getSimpleName().toString(); String sig = TypeUtils.getJavaSignature(method); String desc = TypeUtils.getDescriptor(method); if (remap != Remap.ONLY_PREFIXED) { MethodHandle mixinMethod = this.mixin.getHandle().findMethod(name, sig); if (mixinMethod != null) { this.addInterfaceMethodMapping(remap, iface, null, mixinMethod, name, desc); } } if (prefix != null) { MethodHandle prefixedMixinMethod = this.mixin.getHandle().findMethod(prefix + name, sig); if (prefixedMixinMethod != null) { this.addInterfaceMethodMapping(remap, iface, prefix, prefixedMixinMethod, name, desc); } } } /** * Searches for obfuscation mappings for the specified interface method and * adds mappings to the output set if obfuscation mappings are found for the * method * * @param remap Remapping strategy * @param iface Interface to enumerate * @param prefix Prefix declared in the soft-implements decoration * @param method Mixin method * @param name Undecorated interface method name * @param desc Interface method descriptor */ private void addInterfaceMethodMapping(Remap remap, TypeHandle iface, String prefix, MethodHandle method, String name, String desc) { MappingMethod mapping = new MappingMethod(iface.getName(), name, desc); ObfuscationData<MappingMethod> obfData = this.obf.getDataProvider().getObfMethod(mapping); if (obfData.isEmpty()) { if (remap.forceRemap()) { this.ap.printMessage(Kind.ERROR, "No obfuscation mapping for soft-implementing method", method.getElement()); } return; } this.addMethodMappings(method.getName(), desc, this.applyPrefix(obfData, prefix)); } /** * Apply the specified name prefix to all mappings in an obfuscation data * set * * @param data input data * @param prefix prefix to apply * @return modified mapping set or original mapping set if prefix is null */ private ObfuscationData<MappingMethod> applyPrefix(ObfuscationData<MappingMethod> data, String prefix) { if (prefix == null) { return data; } ObfuscationData<MappingMethod> prefixed = new ObfuscationData<MappingMethod>(); for (ObfuscationType type : data) { MappingMethod mapping = data.get(type); prefixed.put(type, mapping.addPrefix(prefix)); } return prefixed; } }