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

pl.touk.nussknacker.engine.api.process.ClassExtractionSettings.scala Maven / Gradle / Ivy

The newest version!
package pl.touk.nussknacker.engine.api.process

import cats.data.NonEmptyList
import io.circe.{Decoder, Encoder}
import pl.touk.nussknacker.engine.api.definition.ParameterEditor
import pl.touk.nussknacker.engine.api.typed.supertype.ReturningSingleClassPromotionStrategy
import pl.touk.nussknacker.engine.api.typed.typing.Typed
import pl.touk.nussknacker.engine.api.{Hidden, HideToString}

import java.lang.reflect.{AccessibleObject, Member, Method}
import java.text.NumberFormat
import java.time.Clock
import java.time.chrono.{ChronoLocalDate, ChronoLocalDateTime, ChronoZonedDateTime}
import java.time.temporal.{ChronoUnit, Temporal, TemporalAccessor}
import java.util
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import java.util.{Calendar, Date, Optional, UUID}

// TODO: Rename to ClassDefinitionDiscoverySettings
/**
  * Settings for class extraction which is done to handle e.g. syntax suggestions in UI
  *
  * One-liner that will help to find list of interesting methods in class:
  * {{{
  * classOf[CLASS_NAME].getMethods.filterNot(m => java.lang.reflect.Modifier.isStatic(m.getModifiers)).map(_.getName)
  *   .filterNot(Set("wait", "notify", "notifyAll", "equals", "hashCode", "getClass").contains).distinct.sorted.mkString("|")
  * }}}
  *
  * @param excludeClassPredicates - sequence of predicates to determine hidden classes
  * @param excludeClassMemberPredicates - sequence of predicates to determine excluded class members - will be
  *                                       used all predicates that matches given class
  * @param includeClassMemberPredicates - sequence of predicates to determine included class members - will be
  *                                       used all predicates that matches given class. If none is matching,
  *                                       all non-excluded members will be visible.
  * @param propertyExtractionStrategy - strategy for extraction property based on getter. It can be defined
  *                                     what will happen if some class has 'getField' or 'isField' method.
  *                                     It can be: added 'field' property next to 'getField', replaced 'getField' with
  *                                     'field' or leaved as it is.
  */
case class ClassExtractionSettings(
    excludeClassPredicates: Seq[ClassPredicate],
    excludeClassMemberPredicates: Seq[ClassMemberPredicate],
    includeClassMemberPredicates: Seq[ClassMemberPredicate],
    typingFunctionRules: Seq[TypingFunctionRule],
    propertyExtractionStrategy: PropertyFromGetterExtractionStrategy
) {

  def isHidden(clazz: Class[_]): Boolean =
    excludeClassPredicates.exists(_.matches(clazz))

  def visibleMembersPredicate(clazz: Class[_]): VisibleMembersPredicate = {
    VisibleMembersPredicate(
      excludeClassMemberPredicates.filter(p => p.matchesClass(clazz)),
      NonEmptyList.fromList(includeClassMemberPredicates.filter(p => p.matchesClass(clazz)).toList)
    )
  }

  def typingFunction(clazz: Class[_], member: Member): Option[TypingFunctionForClassMember] =
    typingFunctionRules.collectFirst {
      case rule if rule.matchesClassMember(clazz, member) => rule.typingFunction
    }

}

case class VisibleMembersPredicate(
    excludePredicates: Seq[ClassMemberPredicate],
    includePredicates: Option[NonEmptyList[ClassMemberPredicate]]
) {

  def shouldBeVisible(member: Member): Boolean =
    !excludePredicates.exists(_.matchesMember(member)) && includePredicates.forall(_.exists(_.matchesMember(member)))

}

sealed trait PropertyFromGetterExtractionStrategy

object PropertyFromGetterExtractionStrategy {

  case object AddPropertyNextToGetter extends PropertyFromGetterExtractionStrategy

  case object ReplaceGetterWithProperty extends PropertyFromGetterExtractionStrategy

  case object DoNothing extends PropertyFromGetterExtractionStrategy

}

object ClassExtractionSettings {

  val ToStringMethod: String = "toString"

  val Default: ClassExtractionSettings = ClassExtractionSettings(
    DefaultExcludedClasses,
    DefaultExcludedMembers,
    DefaultIncludedMembers,
    DefaultTypingFunctionRules,
    // TODO: We should change this to ReplaceGetterWithProperty which is more natural and which not introduces ambiguity
    PropertyFromGetterExtractionStrategy.AddPropertyNextToGetter
  )

  lazy val DefaultExcludedClasses: List[ClassPredicate] = ExcludedStdClasses ++ ExcludedExtraClasses

  lazy val ExcludedStdClasses: List[ClassPredicate] =
    ExcludedVoidClasses ++ ExcludedCollectionFunctionalClasses ++ ExcludedTimeClasses ++
      List(
        // In case if someone use it for kind of meta programming
        ExactClassPredicate[Class[_]],

        // we want only boxed types
        ClassPredicate { case cl => cl.isPrimitive },
        ExactClassPredicate[ReturningSingleClassPromotionStrategy],
        // We use this type only programmable
        ClassNamePredicate("pl.touk.nussknacker.engine.spel.SpelExpressionRepr"),
      )

  lazy val ExcludedCollectionFunctionalClasses: List[ClassPredicate] = List(
    // In case if someone return function for lazy evaluation purpose
    BasePackagePredicate("java.util.function"),
    ClassNamePrefixPredicate("scala.Function"),
    BasePackagePredicate("java.util.stream"),
    // Scala collections are cumbersome to use with Spel
    BasePackagePredicate("scala.collection"),
    ClassNamePrefixPredicate("scala.Tuple"),
    ExactClassPredicate[Option[_]],
  )

  lazy val ExcludedVoidClasses: List[ClassPredicate] = List(
    // Void types
    ClassNamePredicate("void", "java.lang.Void", "scala.runtime.BoxedUnit"),
    ClassNamePrefixPredicate("scala.Unit.*"),
  )

  lazy val ExcludedTimeClasses: List[ClassPredicate] = List(
    // we want to have Chrono*Time classes, as many parameters/return types of Local/ZonedDate(Time) use them
    ExceptOfClassesPredicate(
      BasePackagePredicate("java.time.chrono"),
      ExactClassPredicate(classOf[ChronoLocalDateTime[_]], classOf[ChronoLocalDate], classOf[ChronoZonedDateTime[_]])
    ),
    ExceptOfClassesPredicate(
      BasePackagePredicate("java.time.temporal"),
      ExactClassPredicate(classOf[ChronoUnit], classOf[Temporal], classOf[TemporalAccessor])
    ),
    ExactClassPredicate(classOf[Clock]),
    BasePackagePredicate("java.time.zone"),
  )

  lazy val ExcludedExtraClasses: List[ClassPredicate] =
    List(
      // In case if there is some public method with flink's TypeInformation on serialization purpose
      BasePackagePredicate("org.apache.flink.api.common.typeinfo"),
      // java xml api is not easy to use without additional helpers, so we will skip these classes
      BasePackagePredicate("javax.xml"),
      // Not sure why these below exclusions are TODO describe why they should be here or remove it
      BasePackagePredicate("dispatch"),
      BasePackagePredicate("cats"),
      ExactClassPredicate(classOf[Decoder[_]], classOf[Encoder[_]]),
      // If cronutils are included to use Cron editor, we don't want quite a lot of unnecessary classes
      BasePackagePredicate("com.cronutils.model.field")
    )

  lazy val DefaultExcludedMembers: List[ClassMemberPredicate] =
    CommonExcludedMembers ++ KafkaExcludedMembers ++ AvroExcludedMembers ++ JavaTimeExcludeMembers :+
      ReturnMemberPredicate(
        SuperClassPredicate(
          ExactClassPredicate(
            classOf[Decoder[_]],
            classOf[Encoder[_]],
            classOf[ParameterEditor]
          )
        )
      )

  lazy val CommonExcludedMembers: List[ClassMemberPredicate] =
    List(
      // We want to hide all technical methods in every class, toString can be useful so we will leave it
      AllMembersPredicate(classOf[DumpCaseClass], Set(ToStringMethod)),
      ClassMemberPredicate(
        ClassPredicate { case _ => true },
        { case m =>
          m.getName.contains("$")
        }
      ),
      ClassMemberPredicate(
        ClassPredicate { case _ => true },
        { case m: Member with AccessibleObject =>
          m.getAnnotation(classOf[Hidden]) != null
        }
      ),
      ClassMemberPredicate(
        ClassPredicate { case cl => classOf[HideToString].isAssignableFrom(cl) },
        { case m: Method =>
          m.getName == ToStringMethod && m.getParameterCount == 0
        }
      ),
      ClassMemberPredicate(
        ClassPredicate { case cl => cl.isEnum },
        { case m: Method =>
          List("declaringClass", "getDeclaringClass").contains(m.getName)
        }
      )
    )

  lazy val KafkaExcludedMembers: List[ClassMemberPredicate] = List(
    MemberNamePredicate(
      SuperClassPredicate(ClassNamePredicate("pl.touk.nussknacker.engine.kafka.source.InputMeta")),
      Set("withType", "apply", "keyParameterName")
    )
  )

  lazy val AvroExcludedMembers: List[ClassMemberPredicate] =
    List(
      MemberNamePredicate(
        SuperClassPredicate(ClassNamePredicate("org.apache.avro.generic.IndexedRecord")),
        Set("getSchema", "compareTo", "put")
      )
    )

  lazy val JavaTimeExcludeMembers: List[ClassMemberPredicate] =
    List(
      MemberNamePredicate(
        SuperClassPredicate(ClassNamePredicate("java.time.temporal.TemporalAccessor")),
        Set("adjustInto", "from")
      )
    )

  lazy val DefaultIncludedMembers: List[ClassMemberPredicate] =
    IncludedUtilsMembers ++ IncludedSerializableMembers ++ IncludedStdMembers

  lazy val IncludedStdMembers: List[ClassMemberPredicate] =
    List(
      // For other std types we don't want to see anything but toString method
      MemberNamePredicate(ClassNamePrefixPredicate("java."), Set(ToStringMethod)),
      MemberNamePredicate(ClassNamePrefixPredicate("scala."), Set(ToStringMethod))
    )

  lazy val IncludedUtilsMembers: List[ClassMemberPredicate] =
    List(
      // For numeric types, strings an collections, date types we want to see all useful methods - we need this explicitly define here because
      // we have another, more general rule: IncludedStdMembers and both predicates are composed
      ClassMemberPredicate(
        SuperClassPredicate(
          ExactClassPredicate(
            classOf[java.lang.Boolean],
            classOf[Number],
            classOf[Date],
            classOf[Calendar],
            classOf[TimeUnit]
          )
        ),
        { case _ => true }
      ),
      ClassMemberPredicate(ClassNamePrefixPredicate("java.time."), { case _ => true }),
      ClassMemberPredicate(ClassNamePrefixPredicate("scala.concurrent.duration."), { case _ => true }),
      MemberNamePatternPredicate(
        SuperClassPredicate(ExactClassPredicate[CharSequence]),
        Pattern.compile(
          s"charAt|compareTo.*|concat|contains|endsWith|equalsIgnoreCase|format|indexOf|isBlank|isEmpty|join|lastIndexOf|length|matches|" +
            s"replace|replaceAll|replaceFirst|split|startsWith|strip.*|substring|toLowerCase|toUpperCase|trim|$ToStringMethod"
        )
      ),
      MemberNamePatternPredicate(
        SuperClassPredicate(ExactClassPredicate[NumberFormat]),
        Pattern.compile(s"get.*Instance|format|parse")
      ),
      MemberNamePredicate(
        SuperClassPredicate(ExactClassPredicate[util.Collection[_]]),
        Set("contains", "containsAll", "get", "indexOf", "isEmpty", "lastIndexOf", "size")
      ),
      MemberNamePredicate(
        SuperClassPredicate(ExactClassPredicate[util.Map[_, _]]),
        Set("containsKey", "containsValue", "get", "getOrDefault", "isEmpty", "size", "values", "keySet")
      ),
      MemberNamePredicate(
        SuperClassPredicate(ExactClassPredicate[util.Map.Entry[_, _]]),
        Set("getKey", "getValue")
      ),
      MemberNamePredicate(
        SuperClassPredicate(ExactClassPredicate[Optional[_]]),
        Set("get", "isEmpty", "isPresent", "orElse")
      ),
      MemberNamePredicate(
        SuperClassPredicate(ExactClassPredicate[UUID]),
        Set(
          "clockSequence",
          "randomUUID",
          "fromString",
          "getLeastSignificantBits",
          "getMostSignificantBits",
          "node",
          "timestamp",
          ToStringMethod,
          "variant",
          "version"
        )
      ),
      MemberNamePredicate(
        SuperClassPredicate(ExactClassPredicate(classOf[Iterable[_]], classOf[Option[_]])),
        Set(
          "apply",
          "applyOrElse",
          "contains",
          "get",
          "getOrDefault",
          "head",
          "indexOf",
          "isDefined",
          "isEmpty",
          "nonEmpty",
          "orNull",
          "size",
          "tail",
          "values",
          "keys",
          "diff"
        )
      )
    )

  lazy val IncludedSerializableMembers: List[ClassMemberPredicate] =
    List(
      MemberNamePredicate(ClassNamePrefixPredicate("scala.xml."), Set(ToStringMethod)),
      MemberNamePredicate(
        ClassNamePrefixPredicate("io.circe."),
        Set("noSpaces", "noSpacesSortKeys", "spaces2", "spaces2SortKeys", "spaces4", "spaces4SortKeys", ToStringMethod)
      ),
    )

  lazy val DefaultTypingFunctionRules: List[TypingFunctionRule] =
    List(
      TypingFunctionRule(
        MemberNamePredicate(
          SuperClassPredicate(ExactClassPredicate[util.Map[_, _]]),
          Set("get", "getOrDefault")
        ),
        TypingFunctionForClassMember.returnGenericParameterOnPosition(genericParamPosition = 1)
      ),
      TypingFunctionRule(
        MemberNamePredicate(
          SuperClassPredicate(ExactClassPredicate[util.Map[_, _]]),
          Set("values")
        ),
        TypingFunctionForClassMember.returnGenericParameterOnPositionWrapped(
          genericParamPosition = 1,
          wrapGenericParam = valueType => Typed.genericTypeClass[util.Collection[_]](List(valueType))
        )
      ),
      TypingFunctionRule(
        MemberNamePredicate(
          SuperClassPredicate(ExactClassPredicate[util.Map[_, _]]),
          Set("keySet")
        ),
        TypingFunctionForClassMember.returnGenericParameterOnPositionWrapped(
          genericParamPosition = 0,
          wrapGenericParam = valueType => Typed.genericTypeClass[util.Set[_]](List(valueType))
        )
      ),
      TypingFunctionRule(
        MemberNamePredicate(
          SuperClassPredicate(ExactClassPredicate[util.Collection[_]]),
          Set("get")
        ),
        TypingFunctionForClassMember.returnGenericParameterOnPosition(genericParamPosition = 0)
      ),
      TypingFunctionRule(
        MemberNamePredicate(
          SuperClassPredicate(ExactClassPredicate[util.Optional[_]]),
          Set("get", "orElse")
        ),
        TypingFunctionForClassMember.returnGenericParameterOnPosition(genericParamPosition = 0)
      ),
      TypingFunctionRule(
        MemberNamePredicate(
          SuperClassPredicate(ClassNamePredicate("org.apache.avro.generic.GenericRecord")),
          Set("get")
        ),
        TypingFunctionForClassMember.returnRecordFieldsGenericParameterOnPositionWrapped(
          genericParamPosition = 1,
          wrapGenericParam = identity
        )
      )
    )

  private case class DumpCaseClass()

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy