All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modelmapper.internal.ExplicitMappingVisitor Maven / Gradle / Ivy

There is a newer version: 3.2.1
Show newest version
package org.modelmapper.internal;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.modelmapper.internal.PropertyInfoImpl.FieldPropertyInfo;
import org.modelmapper.internal.util.Members;
import org.modelmapper.internal.util.Primitives;
import org.modelmapper.internal.util.Types;
import org.modelmapper.spi.NameableType;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;

/**
 * Visits explicitly declared mappings, capturing mapping information.
 * 
 * @author Jonathan Halterman
 */
public class ExplicitMappingVisitor extends ClassVisitor {
  private static final String SOURCE_FIELD = "source";
  private static final String DEST_FIELD = "destination";
  private static final String PROXY_FIELD_DESC = "Ljava/lang/Object;";
  private static final String CONFIGURE_METHOD = "configure";
  private static final String CONFIGURE_METHOD_DESC = "()V";

  private static final String MAP_METHOD = "map";
  private static final String SKIP_METHOD = "skip";
  private static final String MAP_EXPR_OWNER_PREFIX = "org/modelmapper/builder";
  private static final String MAP_DEST_METHOD_DESC = "()Ljava/lang/Object;";
  private static final String MAP_SOURCE_METHOD_DESC = "(Ljava/lang/Object;)Ljava/lang/Object;";
  private static final String SKIP_DEST_METHOD_DESC = "(Ljava/lang/Object;)V";
  private static final String MAP_BOTH_METHOD_DESC = "(Ljava/lang/Object;Ljava/lang/Object;)V";

  private static final Pattern MUTATOR_METHOD_DESC_PATTERN = Pattern.compile("^\\(.+\\)(.+)$");

  private final Errors errors;
  private final InheritingConfiguration config;
  private final String propMapClassInternalName;
  private final ClassLoader propertyMapClassLoader;
  private final Set syntheticFields = new HashSet();

  /** Result mappings */
  final List mappings = new ArrayList();

  static class VisitedMapping {
    List sourceAccessors = new ArrayList();
    List destinationAccessors = new ArrayList();
    List destinationMutators = new ArrayList();
  }

  public ExplicitMappingVisitor(Errors errors, InheritingConfiguration config,
      String propertyMapClassName, String destinationClassName, ClassLoader propertyMapClassLoader) {
    super(Opcodes.ASM5);
    this.errors = errors;
    this.config = config;
    propMapClassInternalName = propertyMapClassName.replace('.', '/');
    this.propertyMapClassLoader = propertyMapClassLoader;
  }

  @Override
  public FieldVisitor visitField(int access, String name, String desc, String signature,
      Object value) {
    if ((access & Opcodes.ACC_SYNTHETIC) == Opcodes.ACC_SYNTHETIC)
      syntheticFields.add(name);
    return null;
  }

  @Override
  public MethodVisitor visitMethod(int access, String name, String desc, String signature,
      String[] exceptions) {
    if (name.equals(CONFIGURE_METHOD) && desc.equals(CONFIGURE_METHOD_DESC))
      return new MappingCapturingVisitor();
    return null;
  }

  private class MappingCapturingVisitor extends MethodVisitor {
    private final List instructions = new ArrayList();

    /** Per mapping state */
    private VisitedMapping mapping = new VisitedMapping();
    /** 0=none, 1=source, 2=destination */
    private int subjectType;
    /** 0=none, 1=map(), 2=map(Object), 3=skip(Object) */
    private int mapType;

    private MappingCapturingVisitor() {
      super(Opcodes.ASM5);
    }

    @Override
    public void visitInsn(int opcode) {
      if (opcode == Opcodes.RETURN)
        instructions.add(new InsnNode(opcode));
    }

    @Override
    public void visitVarInsn(int opcode, int var) {
      // If ALOAD and previous instruction was a map(Object, Object) or mutator
      if (opcode == Opcodes.ALOAD) {
        if (!instructions.isEmpty()) {
          AbstractInsnNode previous = instructions.get(instructions.size() - 1);
          if (isMethodInvocation(previous)) {
            MethodInsnNode mn = (MethodInsnNode) previous;
            if (isMapBothMethod(mn) || isMutator(mn))
              instructions.add(new InsnNode(opcode));
          }
        }
      }
    }

    @Override
    public void visitFieldInsn(final int opcode, final String owner, final String name,
        final String desc) {
      // If GETFIELD and not owned by property map or not synthetic
      if (opcode == Opcodes.GETFIELD
          && (!owner.equals(propMapClassInternalName) || !syntheticFields.contains(name)))
        instructions.add(new FieldInsnNode(opcode, owner, name, desc));
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
      boolean isSourceMethod = owner.equals(propMapClassInternalName)
          && (name.equals(SOURCE_FIELD) || name.equals(DEST_FIELD));

      // If not special and not source method invocation and owner is not a primitive wrapper
      if (opcode != Opcodes.INVOKESPECIAL && !isSourceMethod
          && !Primitives.isPrimitiveWrapperInternalName(owner))
        instructions.add(new MethodInsnNode(opcode, owner, name, desc, itf));
    }

    @Override
    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
      instructions.add(new InvokeDynamicInsnNode(name, desc, bsm, bsmArgs));
    }

    @Override
    public void visitEnd() {
      for (int i = 0; i < instructions.size(); i++) {
        AbstractInsnNode ins = instructions.get(i);

        if (ins.getOpcode() == Opcodes.GETFIELD) {
          FieldInsnNode fn = (FieldInsnNode) ins;

          if (fn.owner.equals(propMapClassInternalName) && fn.desc.equals(PROXY_FIELD_DESC)) {
            if (fn.name.equals(SOURCE_FIELD))
              subjectType = 1;
            else if (fn.name.equals(DEST_FIELD))
              subjectType = 2;
            continue;
          }

          Class ownerType = classFor(fn.owner.replace('/', '.'));
          if (subjectType == 1)
            recordSourceField(ownerType, fieldFor(ownerType, fn));
          else if (subjectType == 2) {
            recordDestinationField(ownerType, fieldFor(ownerType, fn));
          }
        } else if (isMethodInvocation(ins)) {
          MethodInsnNode mn = (MethodInsnNode) ins;

          if (isMapMethod(mn) || isSkipMethod(mn)) {
            if (MAP_DEST_METHOD_DESC.equals(mn.desc)) {
              mapType = 1;
              subjectType = 2;
            } else if (MAP_SOURCE_METHOD_DESC.equals(mn.desc)) {
              // If already recorded destination field
              if (subjectType == 2) {
                recordProperties();
              } else {
                mapType = 2;
                subjectType = 2;
              }
            } else if (MAP_BOTH_METHOD_DESC.equals(mn.desc)) {
              recordProperties();
            } else if (SKIP_DEST_METHOD_DESC.equals(mn.desc)) {
              mapType = 3;
            }
          } else if (subjectType != 0) {
            Class ownerType = classFor(mn.owner.replace('/', '.'));
            Type methodType = Type.getMethodType(mn.desc);

            // If last destination mutator
            if (mapType != 0 && isMutator(mn)) {
              recordDestinationMethod(ownerType, methodFor(ownerType, methodType, mn));
              recordProperties();
            } else {
              if (subjectType == 1)
                recordSourceMethod(ownerType, methodFor(ownerType, methodType, mn));
              else if (subjectType == 2) {
                recordDestinationMethod(ownerType, methodFor(ownerType, methodType, mn));
              }
            }
          }
        } else if (ins.getOpcode() == Opcodes.ALOAD || ins.getOpcode() == Opcodes.RETURN) {
          // If skip(Object)
          if (mapType == 3 && subjectType != 0)
            recordProperties();
          else if (mapType != 0)
            errors.missingDestination();
        }
      }
    }

    private boolean isMethodInvocation(AbstractInsnNode ins) {
      return ins.getOpcode() == Opcodes.INVOKEVIRTUAL || ins.getOpcode() == Opcodes.INVOKEINTERFACE;
    }

    private boolean isMapMethod(MethodInsnNode mn) {
      return mn.name.equals(MAP_METHOD)
          && (mn.owner.equals(propMapClassInternalName) || mn.owner.startsWith(MAP_EXPR_OWNER_PREFIX));
    }

    private boolean isSkipMethod(MethodInsnNode mn) {
      return mn.name.equals(SKIP_METHOD)
          && (mn.owner.equals(propMapClassInternalName) || mn.owner.startsWith(MAP_EXPR_OWNER_PREFIX));
    }

    private boolean isMapBothMethod(MethodInsnNode mn) {
      return mn.name.equals(MAP_METHOD)
          && (mn.owner.equals(propMapClassInternalName) || mn.owner.startsWith(MAP_EXPR_OWNER_PREFIX))
          && mn.desc.equals(MAP_BOTH_METHOD_DESC);
    }

    private boolean isMutator(MethodInsnNode mn) {
      Matcher matcher = MUTATOR_METHOD_DESC_PATTERN.matcher(mn.desc);
      if (!matcher.find())
        return false;

      String returnType = matcher.group(1);
      return returnType.equals("V")
          || returnType.contains(mn.owner);
    }

    private void recordProperties() {
      mappings.add(mapping);
      mapping = new VisitedMapping();
      subjectType = 0;
      mapType = 0;
    }

    private void recordSourceMethod(Class type, Method method) {
      assertNotFinal(method);
      if (PropertyInfoResolver.ACCESSORS.isValid(method)) {
        String propertyName = config.getSourceNameTransformer().transform(method.getName(),
            NameableType.METHOD);
        mapping.sourceAccessors.add(PropertyInfoRegistry.accessorFor(type, method, config,
            propertyName));
      } else
        errors.invalidSourceMethod(method);
    }

    private void recordSourceField(Class type, Field field) {
      assertNotFinal(field);
      if (PropertyInfoResolver.FIELDS.isValid(field)) {
        String propertyName = config.getSourceNameTransformer().transform(field.getName(),
            NameableType.FIELD);
        mapping.sourceAccessors.add(PropertyInfoRegistry.fieldPropertyFor(type, field, config,
            propertyName));
      } else
        errors.invalidSourceField(field);
    }

    private void recordDestinationMethod(Class type, Method method) {
      assertNotFinal(method);
      if (PropertyInfoResolver.MUTATORS.isValid(method)) {
        String propertyName = config.getDestinationNameTransformer().transform(method.getName(),
            NameableType.METHOD);
        mapping.destinationMutators.add(PropertyInfoRegistry.mutatorFor(type, method, config,
            propertyName));
      } else if (PropertyInfoResolver.ACCESSORS.isValid(method)) {
        String propertyName = config.getSourceNameTransformer().transform(method.getName(),
            NameableType.METHOD);
        mapping.destinationAccessors.add(PropertyInfoRegistry.accessorFor(type, method, config,
            propertyName));

        // Find mutator corresponding to accessor
        Mutator mutator = TypeInfoRegistry.typeInfoFor(type, config).mutatorForAccessorMethod(
            method.getName());
        if (mutator != null)
          mapping.destinationMutators.add(mutator);
        else
          errors.missingMutatorForAccessor(method);
      } else
        errors.invalidDestinationMethod(method);
    }

    private void recordDestinationField(Class type, Field field) {
      assertNotFinal(field);
      if (PropertyInfoResolver.FIELDS.isValid(field)) {
        String propertyName = config.getDestinationNameTransformer().transform(field.getName(),
            NameableType.FIELD);
        FieldPropertyInfo propertyInfo = PropertyInfoRegistry.fieldPropertyFor(type, field, config,
            propertyName);
        mapping.destinationAccessors.add(propertyInfo);
        mapping.destinationMutators.add(propertyInfo);
      } else
        errors.invalidDestinationField(field);
    }

    private void assertNotFinal(Member member) {
      if (Modifier.isFinal(member.getDeclaringClass().getModifiers()))
        errors.invocationAgainstFinalClass(member.getDeclaringClass());
      if (member instanceof Method && Modifier.isFinal(member.getModifiers()))
        errors.invocationAgainstFinalMethod(member);
    }
  }

  private Field fieldFor(Class type, FieldInsnNode fn) {
    return Members.fieldFor(type, fn.name);
  }

  private Method methodFor(Class type, Type methodType, MethodInsnNode mn) {
    Type[] argumentTypes = methodType.getArgumentTypes();
    Class[] paramTypes = new Class[argumentTypes.length];
    for (int i = 0; i < paramTypes.length; i++) {
      try {
        paramTypes[i] = Types.classFor(argumentTypes[i], propertyMapClassLoader);
      } catch (ClassNotFoundException e) {
        throw errors.errorResolvingClass(e, argumentTypes[i].getClassName()).toException();
      }
    }
    return Members.methodFor(type, mn.name, paramTypes);
  }

  private Class classFor(String className) {
    try {
      return Class.forName(className, true, propertyMapClassLoader);
    } catch (ClassNotFoundException e) {
      throw errors.errorResolvingClass(e, className).toException();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy