/* * 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.injection.struct; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; 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.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionPointException; import org.spongepowered.asm.mixin.refmap.IMixinContext; import com.google.common.base.Joiner; import com.google.common.base.Strings; /** * Data read from an {@link org.spongepowered.asm.mixin.injection.At} annotation * and passed into an InjectionPoint ctor */ public class InjectionPointData { /** * Regex for recognising at declarations */ private static final Pattern AT_PATTERN = InjectionPointData.createPattern(); /** * K/V arguments parsed from the "args" node in the {@link At} annotation */ private final Map<String, String> args = new HashMap<String, String>(); /** * Mixin */ private final IMixinContext context; /** * Injector callback */ private final MethodNode method; /** * Parent annotation */ private final AnnotationNode parent; /** * At arg */ private final String at; /** * Parsed from the at argument, the injector type (shortcode or class name) */ private final String type; /** * Selector parsed from the at argument, only used by slices */ private final Selector selector; /** * Target */ private final String target; /** * Slice id specified in the annotation, only used by slices */ private final String slice; /** * Ordinal */ private final int ordinal; /** * Opcode */ private final int opcode; public InjectionPointData(IMixinContext context, MethodNode method, AnnotationNode parent, String at, List<String> args, String target, String slice, int ordinal, int opcode) { this.context = context; this.method = method; this.parent = parent; this.at = at; this.target = target; this.slice = Strings.nullToEmpty(slice); this.ordinal = Math.max(-1, ordinal); this.opcode = opcode; this.parseArgs(args); this.args.put("target", target); this.args.put("ordinal", String.valueOf(ordinal)); this.args.put("opcode", String.valueOf(opcode)); Matcher matcher = InjectionPointData.AT_PATTERN.matcher(at); this.type = InjectionPointData.parseType(matcher, at); this.selector = InjectionPointData.parseSelector(matcher); } private void parseArgs(List<String> args) { if (args == null) { return; } for (String arg : args) { if (arg != null) { int eqPos = arg.indexOf('='); if (eqPos > -1) { this.args.put(arg.substring(0, eqPos), arg.substring(eqPos + 1)); } else { this.args.put(arg, ""); } } } } /** * Get the <tt>at</tt> value on the injector */ public String getAt() { return this.at; } /** * Get the parsed constructor <tt>type</tt> for this injector */ public String getType() { return this.type; } /** * Get the selector value parsed from the injector */ public Selector getSelector() { return this.selector; } /** * Get the context */ public IMixinContext getContext() { return this.context; } /** * Get the annotated method */ public MethodNode getMethod() { return this.method; } /** * Get the return type of the annotated method */ public Type getMethodReturnType() { return Type.getReturnType(this.method.desc); } /** * Get the root annotation (eg. {@link Inject}) */ public AnnotationNode getParent() { return this.parent; } /** * Get the slice id specified on the injector */ public String getSlice() { return this.slice; } public LocalVariableDiscriminator getLocalVariableDiscriminator() { return LocalVariableDiscriminator.parse(this.parent); } /** * Get the supplied value from the named args, return defaultValue if the * arg is not set * * @param key argument name * @param defaultValue value to return if the arg is not set * @return argument value or default if not set */ public String get(String key, String defaultValue) { String value = this.args.get(key); return value != null ? value : defaultValue; } /** * Get the supplied value from the named args, return defaultValue if the * arg is not set * * @param key argument name * @param defaultValue value to return if the arg is not set * @return argument value or default if not set */ public int get(String key, int defaultValue) { return InjectionPointData.parseInt(this.get(key, String.valueOf(defaultValue)), defaultValue); } /** * Get the supplied value from the named args, return defaultValue if the * arg is not set * * @param key argument name * @param defaultValue value to return if the arg is not set * @return argument value or default if not set */ public boolean get(String key, boolean defaultValue) { return InjectionPointData.parseBoolean(this.get(key, String.valueOf(defaultValue)), defaultValue); } /** * Get the supplied value from the named args as a {@link MemberInfo}, * throws an exception if the argument cannot be parsed as a MemberInfo. * * @param key argument name * @return argument value as a MemberInfo */ public MemberInfo get(String key) { try { return MemberInfo.parseAndValidate(this.get(key, ""), this.context); } catch (InvalidMemberDescriptorException ex) { throw new InvalidInjectionPointException(this.context, "Failed parsing @At(\"%s\").%s descriptor \"%s\" on %s", this.at, key, this.target, InjectionInfo.describeInjector(this.context, this.parent, this.method)); } } /** * Get the target value specified on the injector */ public MemberInfo getTarget() { try { return MemberInfo.parseAndValidate(this.target, this.context); } catch (InvalidMemberDescriptorException ex) { throw new InvalidInjectionPointException(this.context, "Failed parsing @At(\"%s\") descriptor \"%s\" on %s", this.at, this.target, InjectionInfo.describeInjector(this.context, this.parent, this.method)); } } /** * Get the ordinal specified on the injection point */ public int getOrdinal() { return this.ordinal; } /** * Get the opcode specified on the injection point */ public int getOpcode() { return this.opcode; } /** * Get the opcode specified on the injection point or return the default if * no opcode was specified * * @param defaultOpcode opcode to return if none specified * @return opcode or default */ public int getOpcode(int defaultOpcode) { return this.opcode > 0 ? this.opcode : defaultOpcode; } /** * Get the opcode specified on the injection point or return the default if * no opcode was specified or if the specified opcode does not appear in the * supplied list of valid opcodes * * @param defaultOpcode opcode to return if none specified * @param validOpcodes valid opcodes * @return opcode or default */ public int getOpcode(int defaultOpcode, int... validOpcodes) { for (int validOpcode : validOpcodes) { if (this.opcode == validOpcode) { return this.opcode; } } return defaultOpcode; } @Override public String toString() { return this.type; } private static Pattern createPattern() { return Pattern.compile(String.format("^([^:]+):?(%s)?$", Joiner.on('|').join(Selector.values()))); } /** * Parse a constructor type from the supplied <tt>at</tt> string * * @param at at to parse * @return parsed constructor type */ public static String parseType(String at) { Matcher matcher = InjectionPointData.AT_PATTERN.matcher(at); return InjectionPointData.parseType(matcher, at); } private static String parseType(Matcher matcher, String at) { return matcher.matches() ? matcher.group(1) : at; } private static Selector parseSelector(Matcher matcher) { return matcher.matches() && matcher.group(2) != null ? Selector.valueOf(matcher.group(2)) : Selector.DEFAULT; } private static int parseInt(String string, int defaultValue) { try { return Integer.parseInt(string); } catch (Exception ex) { return defaultValue; } } private static boolean parseBoolean(String string, boolean defaultValue) { try { return Boolean.parseBoolean(string); } catch (Exception ex) { return defaultValue; } } }