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

org.elasticsearch.spark.serialization.ReflectionUtils.scala Maven / Gradle / Ivy

There is a newer version: 8.16.0
Show newest version
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.elasticsearch.spark.serialization

import java.beans.Introspector
import java.lang.reflect.Method

import scala.collection.mutable.HashMap
import scala.reflect.runtime.universe._
import org.apache.commons.logging.LogFactory

private[spark] object ReflectionUtils {

  val caseClassCache = new HashMap[Class[_], (Boolean, Iterable[String])]
  val javaBeanCache = new HashMap[Class[_], Array[(String, Method)]]

  //SI-6240
  protected[spark] object ReflectionLock

  private def checkCaseClass(clazz: Class[_]): Boolean = {
    ReflectionLock.synchronized {
      // reliable case class identifier only happens through class symbols...
      runtimeMirror(clazz.getClassLoader()).classSymbol(clazz).isCaseClass
    }
  }

  private def doGetCaseClassInfo(clazz: Class[_]): Iterable[String] = {
    ReflectionLock.synchronized {
      val t = runtimeMirror(clazz.getClassLoader()).classSymbol(clazz).toType
      val decls = try {
        t.getClass.getMethod("decls")
      } catch {
        case _: Throwable => t.getClass.getMethod("declarations")
      }

      val scopes : Iterable[Symbol] = decls.invoke(t).asInstanceOf[Iterable[Symbol]]
      scopes.collect {
        case m: MethodSymbol if m.isCaseAccessor => m.name.toString()
      }
    }
  }

  private def isCaseClassInsideACompanionModule(clazz: Class[_], arity: Int): Boolean = {
    if (!classOf[Serializable].isAssignableFrom(clazz)) {
      false
    }

    // check 'copy' synthetic methods - they are public so go with getMethods
    val copyMethods = clazz.getMethods.collect {
      case m: Method if m.getName.startsWith("copy$default$") => m.getName
    }

    arity == copyMethods.length
  }

  // TODO: this is a hack since we expect the field declaration order to be according to the source but there's no guarantee
  private def caseClassInfoInsideACompanionModule(clazz: Class[_], arity: Int): Iterable[String] = {
    // fields are private so use the 'declared' variant
    var counter: Int = 0
    clazz.getDeclaredFields.collect {
      case field if (counter < arity) => counter += 1; field.getName
    }
  }

  private def doGetCaseClassValues(target: AnyRef, props: Iterable[String]) = {
    val product = target.asInstanceOf[Product].productIterator
    val tuples = for (y <- props) yield (y, product.next)
    tuples.toMap
  }

  private def checkCaseClassCache(p: Product) = {
    caseClassCache.getOrElseUpdate(p.getClass, {
      var isCaseClazz = checkCaseClass(p.getClass)
      var info = if (isCaseClazz) doGetCaseClassInfo(p.getClass) else null
      if (!isCaseClazz) {
        isCaseClazz = isCaseClassInsideACompanionModule(p.getClass, p.productArity)
        if (isCaseClazz) {
          // Todo: Fix this logger usage
          LogFactory.getLog(classOf[ScalaValueWriter]).warn(
              String.format("[%s] is detected as a case class in Java but not in Scala and thus " +
                  "its properties might be detected incorrectly - make sure the @ScalaSignature is available within the class bytecode " +
                  "and/or consider moving the case class from its companion object/module", p.getClass))
        }
        info = if (isCaseClazz) caseClassInfoInsideACompanionModule(p.getClass(), p.productArity) else null
      }

      (isCaseClazz, info)
    })
  }

  def isCaseClass(p: Product) = {
    checkCaseClassCache(p)._1
  }

  def caseClassValues(p: Product) = {
    doGetCaseClassValues(p.asInstanceOf[AnyRef], checkCaseClassCache(p)._2)
  }

  private def checkJavaBeansCache(o: Any) = {
    javaBeanCache.getOrElseUpdate(o.getClass, {
      javaBeansInfo(o.getClass)
    })
  }

  def isJavaBean(value: Any) = {
    !checkJavaBeansCache(value).isEmpty
  }

  def javaBeanAsMap(value: Any) = {
    javaBeansValues(value, checkJavaBeansCache(value))
  }

  private def javaBeansInfo(clazz: Class[_]) = {
    Introspector.getBeanInfo(clazz).getPropertyDescriptors().collect {
      case pd if (pd.getName != "class" && pd.getReadMethod() != null) => (pd.getName, pd.getReadMethod)
    }.sortBy(_._1)
  }

  private def javaBeansValues(target: Any, info: Array[(String, Method)]) = {
    info.map(in => (in._1, in._2.invoke(target))).toMap
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy