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

org.fulib.scenarios.library.ClassModelVisitor Maven / Gradle / Ivy

package org.fulib.scenarios.library;

import org.fulib.scenarios.ast.decl.*;
import org.fulib.scenarios.ast.type.ListType;
import org.fulib.scenarios.ast.type.PrimitiveType;
import org.fulib.scenarios.ast.type.Type;
import org.fulib.scenarios.ast.type.UnresolvedType;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.function.Consumer;

public class ClassModelVisitor extends ClassVisitor
{
   // =============== Constants ===============

   private static final String[] SETTER_PREFIXES = new String[] { "set", "with", "without" };

   // =============== Fields ===============

   private final ExternalClassDecl classDecl;

   // =============== Constructors ===============

   public ClassModelVisitor(ExternalClassDecl classDecl)
   {
      super(Opcodes.ASM7);
      this.classDecl = classDecl;
   }

   // =============== Methods ===============

   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
   {
      this.classDecl.setName(getSimpleName(name));

      if (superName != null)
      {
         this.classDecl.setSuperType(UnresolvedType.of(getSimpleName(superName), superName, false));
      }
   }

   private static String getSimpleName(String name)
   {
      final int slashIndex = name.lastIndexOf('/');
      return slashIndex < 0 ? name : name.substring(slashIndex + 1);
   }

   // all fields will be processed before the first method

   @Override
   public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
   {
      if (name.isEmpty() || "".equals(name) || "".equals(name))
      {
         // don't care about constructors or class initializer
         return null;
      }

      final int length = name.length();
      if (tryCreateAttribute(name, descriptor, signature, "get"))
      {
         return null;
      }
      if (tryCreateAttribute(name, descriptor, signature, "is"))
      {
         return null;
      }
      for (String prefix : SETTER_PREFIXES)
      {
         final int prefixLength = prefix.length();
         if (length > prefixLength && name.startsWith(prefix) && !Character.isLowerCase(name.charAt(prefixLength)))
         {
            // ignore setters and withers
            return null;
         }
      }

      // don't care about non-public or static methods
      if ((access & Modifier.PUBLIC) != 0 && (access & Modifier.STATIC) == 0)
      {
         this.tryCreateMethod(name, descriptor);
      }

      return null;
   }

   private boolean tryCreateAttribute(String name, String descriptor, String signature, String prefix)
   {
      final int prefixLength = prefix.length();
      if (name.startsWith(prefix) && name.length() > prefixLength && !Character.isLowerCase(name.charAt(prefixLength)))
      {
         // convert getters to attributes
         final String attributeName =
            Character.toLowerCase(name.charAt(prefixLength)) + name.substring(prefixLength + 1);
         this.tryCreateAttribute(attributeName, descriptor, signature);
         return true;
      }
      return false;
   }

   @Override
   public void visitEnd()
   {
      this.classDecl.markUnresolved();
   }

   private void tryCreateAttribute(String name, String descriptor, String signature)
   {
      try
      {
         this.createAttribute(name, descriptor, signature);
      }
      catch (UnsupportedOperationException ignored)
      {
      }
   }

   private void createAttribute(String name, String descriptor, String signature)
   {
      final int returnTypeIndex = descriptor.indexOf(')') + 1;

      // We only create attributes at this point because types are unresolved.
      // After external type resolution in NameResolver.visit(ScenarioGroup), we convert them
      // to associations as necessary.

      if (descriptor.endsWith(")Ljava/util/List;") || descriptor.endsWith(")Ljava/util/ArrayList;"))
      {
         final ListType listType = ListType.of(null);
         final int elementTypeIndex = signature.indexOf('<', returnTypeIndex) + 1;
         parseType(signature, elementTypeIndex, listType::setElementType);

         final AttributeDecl attribute = AttributeDecl.of(this.classDecl, name, listType);
         this.classDecl.getAttributes().put(name, attribute);
         return;
      }

      final AttributeDecl attribute = AttributeDecl.of(this.classDecl, name, null);
      parseType(descriptor, returnTypeIndex, attribute::setType);
      this.classDecl.getAttributes().put(name, attribute);
   }

   private void tryCreateMethod(String name, String descriptor)
   {
      try
      {
         final MethodDecl methodDecl = new ExternalMethodDecl(this.classDecl, name, new ArrayList<>(), null, null);
         parseType(descriptor, descriptor.lastIndexOf(')') + 1, methodDecl::setType);

         final ParameterDecl thisParam = new ExternalParameterDecl(methodDecl, "this", this.classDecl.getType());
         methodDecl.getParameters().add(thisParam);

         int index = 1;
         while (descriptor.charAt(index) != ')')
         {
            final ParameterDecl param = new ExternalParameterDecl(methodDecl, null, null);
            index = parseType(descriptor, index, param::setType);
            methodDecl.getParameters().add(param);
         }

         this.classDecl.getMethods().add(methodDecl);
      }
      catch (UnsupportedOperationException ignored)
      {
      }
   }

   // =============== Static Methods ===============

   public static Type parseType(String descriptor, int index)
   {
      final Type[] holder = new Type[1];
      parseType(descriptor, index, t -> holder[0] = t);
      return holder[0];
   }

   public static int parseType(String descriptor, int index, Consumer consumer)
   {
      final char c = descriptor.charAt(index);
      switch (c)
      {
      case '[':
         throw new UnsupportedOperationException("array types");
      case 'T':
         throw new UnsupportedOperationException("generic type variables");
      case 'L':
         final int genericStart = descriptor.indexOf('<', index + 1);
         if (genericStart > 0)
         {
            throw new UnsupportedOperationException("generic type arguments");
         }

         final int semi = descriptor.indexOf(';', index + 1);
         final int slash = descriptor.lastIndexOf('/', semi - 1);
         final String text = descriptor.substring(index + 1, semi);
         final String name = descriptor.substring(Math.max(slash, index) + 1, semi);

         consumer.accept(UnresolvedType.of(name, text, false));
         return semi + 1;
      default:
         consumer.accept(parsePrimitiveType(c));
         return index + 1;
      }
   }

   private static PrimitiveType parsePrimitiveType(char c)
   {
      switch (c)
      {
      // @formatter:off
      case 'V': return PrimitiveType.VOID;
      case 'Z': return PrimitiveType.BOOLEAN;
      case 'B': return PrimitiveType.BYTE;
      case 'S': return PrimitiveType.SHORT;
      case 'C': return PrimitiveType.CHAR;
      case 'I': return PrimitiveType.INT;
      case 'J': return PrimitiveType.LONG;
      case 'F': return PrimitiveType.FLOAT;
      case 'D': return PrimitiveType.DOUBLE;
      // @formatter:on
      }
      throw new UnsupportedOperationException("unknown type char " + c);
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy