/*
* 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.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.annotation.processing.Messager;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.tools.obfuscation.AnnotatedMixinElementHandlerAccessor.AnnotatedElementAccessor;
import org.spongepowered.tools.obfuscation.AnnotatedMixinElementHandlerAccessor.AnnotatedElementInvoker;
import org.spongepowered.tools.obfuscation.AnnotatedMixinElementHandlerInjector.AnnotatedElementInjectionPoint;
import org.spongepowered.tools.obfuscation.AnnotatedMixinElementHandlerInjector.AnnotatedElementInjector;
import org.spongepowered.tools.obfuscation.AnnotatedMixinElementHandlerOverwrite.AnnotatedElementOverwrite;
import org.spongepowered.tools.obfuscation.interfaces.IMixinAnnotationProcessor;
import org.spongepowered.tools.obfuscation.interfaces.IMixinValidator;
import org.spongepowered.tools.obfuscation.interfaces.IMixinValidator.ValidationPass;
import org.spongepowered.tools.obfuscation.interfaces.IObfuscationManager;
import org.spongepowered.tools.obfuscation.interfaces.ITypeHandleProvider;
import org.spongepowered.tools.obfuscation.mapping.IMappingConsumer;
import org.spongepowered.tools.obfuscation.mirror.AnnotationHandle;
import org.spongepowered.tools.obfuscation.mirror.TypeHandle;
import org.spongepowered.tools.obfuscation.struct.InjectorRemap;
/**
* Information about a mixin stored during processing
*/
class AnnotatedMixin {
/**
* Mixin annotation
*/
private final AnnotationHandle annotation;
/**
* Messager
*/
private final Messager messager;
/**
* Type handle provider
*/
private final ITypeHandleProvider typeProvider;
/**
* Manager
*/
private final IObfuscationManager obf;
/**
* Generated mappings
*/
private final IMappingConsumer mappings;
/**
* Mixin class
*/
private final TypeElement mixin;
/**
* Mixin class
*/
private final TypeHandle handle;
/**
* Specified targets
*/
private final List<TypeHandle> targets = new ArrayList<TypeHandle>();
/**
* Target type (for single-target mixins)
*/
private final TypeHandle primaryTarget;
/**
* Mixin class "reference" (bytecode name)
*/
private final String classRef;
/**
* True if we will actually process remappings for this mixin
*/
private final boolean remap;
/**
* True if the target class is allowed to not exist at compile time, we will
* simulate the target in order to do as much validation as is feasible.
*
* <p>Implies <tt>remap=false</tt></p>
*/
private final boolean virtual;
/**
* Overwrite handler
*/
private final AnnotatedMixinElementHandlerOverwrite overwrites;
/**
* Shadow handler
*/
private final AnnotatedMixinElementHandlerShadow shadows;
/**
* Injector handler
*/
private final AnnotatedMixinElementHandlerInjector injectors;
/**
* Accessor handler
*/
private final AnnotatedMixinElementHandlerAccessor accessors;
/**
* Soft implementation handler;
*/
private final AnnotatedMixinElementHandlerSoftImplements softImplements;
public AnnotatedMixin(IMixinAnnotationProcessor ap, TypeElement type) {
this.typeProvider = ap.getTypeProvider();
this.obf = ap.getObfuscationManager();
this.mappings = this.obf.createMappingConsumer();
this.messager = ap;
this.mixin = type;
this.handle = new TypeHandle(type);
this.virtual = this.handle.getAnnotation(Pseudo.class).exists();
this.annotation = this.handle.getAnnotation(Mixin.class);
this.classRef = type.getQualifiedName().toString().replace('.', '/');
this.primaryTarget = this.initTargets();
// this.remap = !this.virtual && this.annotation.getBoolean("remap", true) && this.targets.size() > 0;
this.remap = this.annotation.getBoolean("remap", true) && this.targets.size() > 0;
this.overwrites = new AnnotatedMixinElementHandlerOverwrite(ap, this);
this.shadows = new AnnotatedMixinElementHandlerShadow(ap, this);
this.injectors = new AnnotatedMixinElementHandlerInjector(ap, this);
this.accessors = new AnnotatedMixinElementHandlerAccessor(ap, this);
this.softImplements = new AnnotatedMixinElementHandlerSoftImplements(ap, this);
}
AnnotatedMixin runValidators(ValidationPass pass, Collection<IMixinValidator> validators) {
for (IMixinValidator validator : validators) {
if (!validator.validate(pass, this.mixin, this.annotation, this.targets)) {
break;
}
}
return this;
}
private TypeHandle initTargets() {
TypeHandle primaryTarget = null;
// Public targets, referenced by class
try {
for (TypeMirror target : this.annotation.<TypeMirror>getList()) {
TypeHandle type = new TypeHandle((DeclaredType)target);
if (this.targets.contains(type)) {
continue;
}
this.addTarget(type);
if (primaryTarget == null) {
primaryTarget = type;
}
}
} catch (Exception ex) {
this.printMessage(Kind.WARNING, "Error processing public targets: " + ex.getClass().getName() + ": " + ex.getMessage(), this);
}
// Private targets, referenced by name
try {
for (String privateTarget : this.annotation.<String>getList("targets")) {
TypeHandle type = this.typeProvider.getTypeHandle(privateTarget);
if (this.targets.contains(type)) {
continue;
}
if (this.virtual) {
type = this.typeProvider.getSimulatedHandle(privateTarget, this.mixin.asType());
} else if (type == null) {
this.printMessage(Kind.ERROR, "Mixin target " + privateTarget + " could not be found", this);
return null;
} else if (type.isPublic()) {
this.printMessage(Kind.WARNING, "Mixin target " + privateTarget + " is public and must be specified in value", this);
return null;
}
this.addSoftTarget(type, privateTarget);
if (primaryTarget == null) {
primaryTarget = type;
}
}
} catch (Exception ex) {
this.printMessage(Kind.WARNING, "Error processing private targets: " + ex.getClass().getName() + ": " + ex.getMessage(), this);
}
if (primaryTarget == null) {
this.printMessage(Kind.ERROR, "Mixin has no targets", this);
}
return primaryTarget;
}
/**
* Print a message to the AP messager
*/
private void printMessage(Kind kind, CharSequence msg, AnnotatedMixin mixin) {
this.messager.printMessage(kind, msg, this.mixin, this.annotation.asMirror());
}
private void addSoftTarget(TypeHandle type, String reference) {
ObfuscationData<String> obfClassData = this.obf.getDataProvider().getObfClass(type);
if (!obfClassData.isEmpty()) {
this.obf.getReferenceManager().addClassMapping(this.classRef, reference, obfClassData);
}
this.addTarget(type);
}
private void addTarget(TypeHandle type) {
this.targets.add(type);
}
@Override
public String toString() {
return this.mixin.getSimpleName().toString();
}
public AnnotationHandle getAnnotation() {
return this.annotation;
}
/**
* Get the mixin class
*/
public TypeElement getMixin() {
return this.mixin;
}
/**
* Get the type handle for the mixin class
*/
public TypeHandle getHandle() {
return this.handle;
}
/**
* Get the mixin class reference
*/
public String getClassRef() {
return this.classRef;
}
/**
* Get whether this is an interface mixin
*/
public boolean isInterface() {
return this.mixin.getKind() == ElementKind.INTERFACE;
}
/**
* Get the <em>primary</em> target
*/
@Deprecated
public TypeHandle getPrimaryTarget() {
return this.primaryTarget;
}
/**
* Get the mixin's targets
*/
public List<TypeHandle> getTargets() {
return this.targets;
}
/**
* Get whether this is a multi-target mixin
*/
public boolean isMultiTarget() {
return this.targets.size() > 1;
}
/**
* Get whether to remap annotations in this mixin
*/
public boolean remap() {
return this.remap;
}
public IMappingConsumer getMappings() {
return this.mappings;
}
public void registerOverwrite(ExecutableElement method, AnnotationHandle overwrite) {
this.overwrites.registerOverwrite(new AnnotatedElementOverwrite(method, overwrite));
}
public void registerShadow(VariableElement field, AnnotationHandle shadow, boolean shouldRemap) {
this.shadows.registerShadow(this.shadows.new AnnotatedElementShadowField(field, shadow, shouldRemap));
}
public void registerShadow(ExecutableElement method, AnnotationHandle shadow, boolean shouldRemap) {
this.shadows.registerShadow(this.shadows.new AnnotatedElementShadowMethod(method, shadow, shouldRemap));
}
public void registerInjector(ExecutableElement method, AnnotationHandle inject, InjectorRemap remap) {
this.injectors.registerInjector(new AnnotatedElementInjector(method, inject, remap));
List<AnnotationHandle> ats = inject.getAnnotationList("at");
for (AnnotationHandle at : ats) {
this.registerInjectionPoint(method, inject, at, remap, "@At(%s)");
}
List<AnnotationHandle> slices = inject.getAnnotationList("slice");
for (AnnotationHandle slice : slices) {
String id = slice.<String>getValue("id", "");
AnnotationHandle from = slice.getAnnotation("from");
if (from != null) {
this.registerInjectionPoint(method, inject, from, remap, "@Slice[" + id + "](from=@At(%s))");
}
AnnotationHandle to = slice.getAnnotation("to");
if (to != null) {
this.registerInjectionPoint(method, inject, to, remap, "@Slice[" + id + "](to=@At(%s))");
}
}
}
public void registerInjectionPoint(ExecutableElement element, AnnotationHandle inject, AnnotationHandle at, InjectorRemap remap, String format) {
this.injectors.registerInjectionPoint(new AnnotatedElementInjectionPoint(element, inject, at, remap), format);
}
public void registerAccessor(ExecutableElement element, AnnotationHandle accessor, boolean shouldRemap) {
this.accessors.registerAccessor(new AnnotatedElementAccessor(element, accessor, shouldRemap));
}
public void registerInvoker(ExecutableElement element, AnnotationHandle invoker, boolean shouldRemap) {
this.accessors.registerAccessor(new AnnotatedElementInvoker(element, invoker, shouldRemap));
}
public void registerSoftImplements(AnnotationHandle implementsAnnotation) {
this.softImplements.process(implementsAnnotation);
}
}