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

class.methods.HashCode.scala Maven / Gradle / Ivy

The newest version!
package com.julianpeeters.caseclass.generator

import org.objectweb.asm._
import Opcodes._
import scala.reflect.runtime.universe._

//HashCode has two methods: the main "dump" method, and the helper "matchFields" that it calls
case class HashCode(cw: ClassWriter, var mv: MethodVisitor, caseClassName: String, fieldData: List[EnrichedField]) {

  val userDefinedTypes = ClassStore.generatedClasses.keys.toList

  def dump = {
    mv = cw.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
    mv.visitCode();
    fieldData.length match {
      case 0 => {
        mv.visitFieldInsn(GETSTATIC, "scala/runtime/ScalaRunTime$", "MODULE$", "Lscala/runtime/ScalaRunTime$;");
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, "scala/runtime/ScalaRunTime$", "_hashCode", "(Lscala/Product;)I");
      }
      case x if x > 0 => { //type erase the generics, then check if all the types are from the following list
        if (fieldData.map(fd => fd.fieldType.erasure).forall(t => (List(typeOf[Nothing].erasure, typeOf[Null].erasure, typeOf[Any].erasure, typeOf[AnyRef].erasure, typeOf[Object].erasure, typeOf[String].erasure, typeOf[Option[Any]].erasure, typeOf[List[Any]].erasure) ::: userDefinedTypes).contains(t))) { //if all the valueMembers are in this list (of "empty" types, look different when paired with "real")   
          mv.visitFieldInsn(GETSTATIC, "scala/runtime/ScalaRunTime$", "MODULE$", "Lscala/runtime/ScalaRunTime$;");
          mv.visitVarInsn(ALOAD, 0);
          mv.visitMethodInsn(INVOKEVIRTUAL, "scala/runtime/ScalaRunTime$", "_hashCode", "(Lscala/Product;)I");
        }

        else {
          mv.visitLdcInsn(new Integer(-889275714));
          mv.visitVarInsn(ISTORE, 1);
          mv.visitVarInsn(ILOAD, 1);

          val fields = if (fieldData.map(n => n.fieldType).contains(typeOf[Nothing])) fieldData.reverse.dropWhile(valueMember => valueMember.fieldType != typeOf[Nothing]).reverse; else fieldData

          // if there is more than one non-"empty" type(see the list above), drop all types after the first Nothing.
          fields.foreach(valueMember => matchFields(valueMember))

          fieldData.length match {
            case 1          => mv.visitInsn(ICONST_1);
            case 2          => mv.visitInsn(ICONST_2);
            case 3          => mv.visitInsn(ICONST_3);
            case 4          => mv.visitInsn(ICONST_4);
            case 5          => mv.visitInsn(ICONST_5);
            case x if x > 5 => mv.visitIntInsn(BIPUSH, x);
          }
          mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", "finalizeHash", "(II)I");
        }

        if (!fieldData.map(n => n.fieldType).contains(typeOf[Nothing])) mv.visitInsn(IRETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
      }
    }
  }

  def matchFields(valueMember: EnrichedField) = {  

    valueMember.fieldType match {
      case l if (l =:= typeOf[Byte] | 
                 l =:= typeOf[Char] | 
                 l =:= typeOf[Short] | 
                 l =:= typeOf[Int] | 
                 l =:= typeOf[Long] | 
                 l =:= typeOf[Float] |
                 l =:= typeOf[Double] | 
                 l =:=typeOf[Unit] | 
                 l =:=typeOf[Null] ) => {
        valueMember.fieldType match {
          case  l if (l =:= typeOf[Byte] | l =:= typeOf[Short] | l =:= typeOf[Int] | l =:= typeOf[Char]) => {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKEVIRTUAL, caseClassName, valueMember.fieldName, "()" + valueMember.typeData.typeDescriptor);
          }
          case l if (l =:= typeOf[Long] | l =:= typeOf[Float] | l =:= typeOf[Double]) => {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKEVIRTUAL, caseClassName, valueMember.fieldName, "()" + valueMember.typeData.typeDescriptor);
            mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", valueMember.fieldType + "Hash", "(" + valueMember.typeData.typeDescriptor + ")I");
          }
          case l if (l =:= typeOf[Unit] | l =:=  typeOf[Null]) => {
            mv.visitInsn(ICONST_0);
            mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", "mix", "(II)I");
            mv.visitVarInsn(ISTORE, 1);
            mv.visitVarInsn(ILOAD, 1);
          }
          case _ => error("could not generate hashcode method: unsupported type")
        }
      }

      case b if b =:= typeOf[Boolean] => {
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, caseClassName, valueMember.fieldName, "()" + valueMember.typeData.typeDescriptor);
        val l0 = new Label();
        mv.visitJumpInsn(IFEQ, l0);
        mv.visitIntInsn(SIPUSH, 1231);
        val l1 = new Label();
        mv.visitJumpInsn(GOTO, l1);
        mv.visitLabel(l0);
        mv.visitFrame(Opcodes.F_FULL, 2, Array[Object] (caseClassName, Opcodes.INTEGER), 1, Array[Object] (Opcodes.INTEGER));
        mv.visitIntInsn(SIPUSH, 1237);
        mv.visitLabel(l1);
        mv.visitFrame(Opcodes.F_FULL, 2, Array[Object] (caseClassName, Opcodes.INTEGER), 2, Array[Object] (Opcodes.INTEGER, Opcodes.INTEGER));
        mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", "mix", "(II)I");
        mv.visitVarInsn(ISTORE, 1);
        mv.visitVarInsn(ILOAD, 1);
      }
      //if there were only one valueMember, the "if" statement would have taken care of things
      //so this has to have come after
      case la @ TypeRef(pre, symbol, args) if ((la <:< typeOf[List[Any]] | la <:< typeOf[Option[Any]] | la <:< typeOf[Stream[Any]]) && args.length == 1 ) => { 
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, caseClassName, valueMember.fieldName, "()" + valueMember.typeData.typeDescriptor);
        mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", "anyHash", "(Ljava/lang/Object;)I");
      }
      case s if (s =:= typeOf[String] | s =:= typeOf[Object] | s =:= typeOf[Any] | s =:= typeOf[AnyRef]) => {
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, caseClassName, valueMember.fieldName, "()" + valueMember.typeData.typeDescriptor);
        mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", "anyHash", "(Ljava/lang/Object;)I");
      }
      case n if n =:= typeOf[Nothing] => { //if typeOf[Nothing] is a value member's type, it will be the last one in the list of value members
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, caseClassName, valueMember.fieldName, "()Lscala/runtime/Nothing$;");
        mv.visitInsn(ATHROW);
      }
      // user defined or other case classes
      case name @ TypeRef(pre, symbol, args) if userDefinedTypes.contains(name) => {
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, caseClassName, valueMember.fieldName, "()" + valueMember.typeData.typeDescriptor);
        mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", "anyHash", "(Ljava/lang/Object;)I");
      }

      case _ => error("cannot generate HashCode method: unsupported type")
    }
    //Booleans and Units get special treatment because their ASM lines have a "mix" already
    if (valueMember.fieldType != typeOf[Boolean] && valueMember.fieldType != typeOf[Unit] && valueMember.fieldType != typeOf[Null]) { 

      mv.visitMethodInsn(INVOKESTATIC, "scala/runtime/Statics", "mix", "(II)I");
      mv.visitVarInsn(ISTORE, 1);
      mv.visitVarInsn(ILOAD, 1);
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy