/* * 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.asm.mixin.transformer; import java.util.HashSet; import java.util.Set; import org.spongepowered.asm.lib.Opcodes; import org.spongepowered.asm.lib.Type; import org.spongepowered.asm.lib.tree.AnnotationNode; import org.spongepowered.asm.lib.tree.MethodNode; import org.spongepowered.asm.mixin.Interface; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.transformer.ClassInfo.Method; import org.spongepowered.asm.mixin.transformer.meta.MixinRenamed; import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; import org.spongepowered.asm.util.Annotations; /** * Information about an interface being runtime-patched onto a mixin target * class, see {@link org.spongepowered.asm.mixin.Implements Implements} */ public final class InterfaceInfo extends TreeInfo { /** * Parent mixin */ private final MixinInfo mixin; /** * Prefix for interface methods. Any methods using this prefix must exist in * the target interface */ private final String prefix; /** * Interface being patched */ private final Type iface; /** * True if all methods implementing this interface should be treated as * unique */ private final boolean unique; /** * Method signatures in the interface, lazy loaded */ private Set<String> methods; /** * Make with the new thing already * * @param mixin Parent mixin * @param prefix Method prefix * @param iface Interface to load */ private InterfaceInfo(MixinInfo mixin, String prefix, Type iface, boolean unique) { if (prefix == null || prefix.length() < 2 || !prefix.endsWith("$")) { throw new InvalidMixinException(mixin, String.format("Prefix %s for iface %s is not valid", prefix, iface.toString())); } this.mixin = mixin; this.prefix = prefix; this.iface = iface; this.unique = unique; } /** * Lazy-loaded methods collection initialiser */ private void initMethods() { this.methods = new HashSet<String>(); this.readInterface(this.iface.getInternalName()); } /** * Reads an interface and its super-interfaces and gathers method names in * to the local "methods" collection * * @param ifaceName Name of the interface to read */ private void readInterface(String ifaceName) { ClassInfo interfaceInfo = ClassInfo.forName(ifaceName); for (Method ifaceMethod : interfaceInfo.getMethods()) { this.methods.add(ifaceMethod.toString()); } for (String superIface : interfaceInfo.getInterfaces()) { this.readInterface(superIface); } } /** * Get the prefix string (non null) * * @return the prefix */ public String getPrefix() { return this.prefix; } /** * Get the interface type * * @return interface type */ public Type getIface() { return this.iface; } /** * Get the internal name of the interface * * @return the internal name for the interface */ public String getName() { return this.iface.getClassName(); } /** * Get the internal name of the interface * * @return the internal name for the interface */ public String getInternalName() { return this.iface.getInternalName(); } /** * Get whether all methods for this interface should be treated as unique * * @return true to treat all member methods as unique */ public boolean isUnique() { return this.unique; } /** * Processes a method node in the mixin and renames it if necessary. If the * prefix is found then we verify that the method exists in the target * interface and throw our teddies out of the pram if that's not the case * (replacement behaviour for {@link Override} essentially. * * @param method Method to rename * @return true if the method was remapped */ public boolean renameMethod(MethodNode method) { if (this.methods == null) { this.initMethods(); } if (!method.name.startsWith(this.prefix)) { if (this.methods.contains(method.name + method.desc)) { this.decorateUniqueMethod(method); } return false; } String realName = method.name.substring(this.prefix.length()); String signature = realName + method.desc; if (!this.methods.contains(signature)) { throw new InvalidMixinException(this.mixin, String.format("%s does not exist in target interface %s", realName, this.getName())); } if ((method.access & Opcodes.ACC_PUBLIC) == 0) { throw new InvalidMixinException(this.mixin, String.format("%s cannot implement %s because it is not visible", realName, this.getName())); } Annotations.setVisible(method, MixinRenamed.class, "originalName", method.name, "isInterfaceMember", true); this.decorateUniqueMethod(method); method.name = realName; return true; } /** * Decorate the target method with {@link Unique} if the interface is marked * as unique * * @param method method to decorate */ private void decorateUniqueMethod(MethodNode method) { if (!this.unique) { return; } if (Annotations.getVisible(method, Unique.class) == null) { Annotations.setVisible(method, Unique.class); this.mixin.getClassInfo().findMethod(method).setUnique(true); } } /** * Convert an {@link Interface} annotation node into an * {@link InterfaceInfo} * * @param mixin Parent mixin * @param node Annotation node to process * @return parsed InterfaceInfo object */ static InterfaceInfo fromAnnotation(MixinInfo mixin, AnnotationNode node) { String prefix = Annotations.<String>getValue(node, "prefix"); Type iface = Annotations.<Type>getValue(node, "iface"); Boolean unique = Annotations.<Boolean>getValue(node, "unique"); if (prefix == null || iface == null) { throw new InvalidMixinException(mixin, String.format("@Interface annotation on %s is missing a required parameter", mixin)); } return new InterfaceInfo(mixin, prefix, iface, unique != null ? unique.booleanValue() : false); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } InterfaceInfo that = (InterfaceInfo) o; return this.mixin.equals(that.mixin) && this.prefix.equals(that.prefix) && this.iface.equals(that.iface); } @Override public int hashCode() { int result = this.mixin.hashCode(); result = 31 * result + this.prefix.hashCode(); result = 31 * result + this.iface.hashCode(); return result; } }