package gw.rating.flow.util

uses com.google.common.cache.CacheBuilder
uses com.google.common.collect.ImmutableList
uses com.google.common.collect.ImmutableListMultimap
uses com.google.common.collect.ImmutableMap
uses com.google.common.collect.Maps
uses gw.api.productmodel.ModifierPattern
uses gw.api.productmodel.OptionCovTermPattern
uses gw.api.productmodel.PolicyLinePatternLookup
uses gw.api.rating.flow.VisibleInRateflow
uses gw.entity.IEntityType
uses gw.entity.TypeKey
uses gw.lang.reflect.IPropertyInfo
uses gw.lang.reflect.IType
uses gw.lang.reflect.ITypeLoaderListener
uses gw.lang.reflect.RefreshRequest
uses gw.lang.reflect.TypeSystem
uses gw.pl.persistence.core.Key
uses gw.rating.RateFlowLogger
uses gw.util.concurrent.LockingLazyVar

uses java.util.stream.Collectors
uses java.util.stream.Stream


/**
 * Calculates the list of types and subtypes that should be usable from a rate routine.
 */
class RatingEntityGraphTraverser {
  static final var _rfLogger = RateFlowLogger.Logger

  // Figured this would be big enough that nothing would be evicted
  // and small enough that we don't have run-away memory utilization in unexpected situations
  private static final var maximumCacheSize = 512L

  static final var _cache = CacheBuilder.newBuilder()
      .maximumSize(maximumCacheSize)
      .build<Key, ImmutableListMultimap<CalcRoutineParamName, InScopeUsage>>();

  static final var _filter = LockingLazyVar.make(\-> new TypeUsageFilter())

  private static final var _writableUsageMapCache = CacheBuilder.newBuilder()
      .maximumSize(maximumCacheSize)
      .build<Key, ImmutableListMultimap<CalcRoutineParamName, InScopeUsage>>()

  private static var _typeSystemCallback : ITypeLoaderListener
  final var _allRoots : List<IType>
  final var _policyLine : String

  /**
   * Creates a new traverser with the given roots.
   *
   * @param roots list of root entities to exclude from navigation (if they are contained within other elements)
   */
  construct(roots : List<IType>, policyLinePatternCode : String) {
    _allRoots = roots
    _policyLine = policyLinePatternCode
    // must save a reference because the listener is attached as a weak reference
    // don't need an atomic reference because the listener is attached as a weak reference
    if (_typeSystemCallback == null) {
      _typeSystemCallback = new ITypeLoaderListener() {
        override function refreshedTypes(request : RefreshRequest) {
          _cache.invalidateAll()
          _writableUsageMapCache.invalidateAll()
        }

        override function refreshed() {
          _cache.invalidateAll()
          _writableUsageMapCache.invalidateAll()
        }
      }
      TypeSystem.addTypeLoaderListenerAsWeakRef(_typeSystemCallback)
    }
  }

  /**
   * Creates a new traverser with roots that correspond to the Parameters in the given parameter set.
   *
   * @param params parameter set from which we will derive root entities
   */
  construct(params : CalcRoutineParameterSet) {
    this(params.Parameters.map(\ param -> param.UseWrapper
        ? gw.lang.reflect.TypeSystem.getByRelativeName(param.WrapperClass)
        : gw.lang.reflect.TypeSystem.getByRelativeName(param.ParamType)).toList(), params.PolicyLinePatternCode)
  }

  // internal function, for testability only
  internal static function clearCache() {
    _cache.invalidateAll()
    _writableUsageMapCache.invalidateAll()
  }

  // utility function, internal for testability
  internal static function exclude(policyLinePatternCode : String, p : IPropertyInfo) : boolean {
    return _filter.get().exclude(policyLinePatternCode, p)
  }

  // utility function, internal for testability
  internal static function traverseProperties(rootType : IType) : boolean {
    return _filter.get().traverseProperties(rootType)
  }

  // utility function, internal for testability
  internal function isNavigableEntity(owner: IType, p : IPropertyInfo) : boolean {
    return isNavigable(owner, p, true) && !p.FeatureType.Interfaces.contains(Modifier)
  }

  /**
   * Creates the map of in-scope usages for the parameters that are in scope, using a new instance of the {@link RatingEntityGraphTraverser}.
   */
  static function initializeMapFromParameterSet(parameterSet : CalcRoutineParameterSet): Map<CalcRoutineParamName, List<InScopeUsage>> {
    if (parameterSet != null) {
      return new RatingEntityGraphTraverser(parameterSet).getTargetableElementsForParameterSet(parameterSet)
    }
    return {}
  }

  static function getWritableTargetElements(paramSet : CalcRoutineParameterSet): Map<CalcRoutineParameter, List<InScopeUsage>> {
    if (paramSet == null or paramSet.Parameters.length == 0) {
      return Collections.emptyMap<CalcRoutineParameter, List<InScopeUsage>>();
    }
    var writableUsages = new RatingEntityGraphTraverser(paramSet).getImmutableTargetableElementsForWritableParameterSet(paramSet)
    var writableElementsMapBuilder = ImmutableMap.builder<CalcRoutineParameter, List<InScopeUsage>>()
    for (param in paramSet.Parameters) {
      var inScopeUsages = writableUsages.get(param.Code)
      if (inScopeUsages == null){
        _rfLogger.error("Parameter not found in cache " + param.Code)
      } else {
        writableElementsMapBuilder.put(param, inScopeUsages.copy())
      }
    }
    return writableElementsMapBuilder.build()
  }

  function getWritableTargetElementsForParameter(parameter: CalcRoutineParameter): List<InScopeUsage> {
    return getImmutableTargetableElementsForWritableParameterSet(parameter.CalcRoutineParameterSet).get(parameter.Code).copy()
  }

  function getTargetableElementsForParameter(parameter: CalcRoutineParameter): List<InScopeUsage> {
    return getImmutableTargetableElementsForParameterSet(parameter.CalcRoutineParameterSet).get(parameter.Code).copy()
  }

  /**
   * @return an immutable map of all usages for each parameter in the parameter set that are targetable in a routine.
   */
  function getImmutableTargetableElementsForParameterSet(paramSet : CalcRoutineParameterSet) : ImmutableListMultimap<CalcRoutineParamName, InScopeUsage> {
    if (paramSet == null) {
      return ImmutableListMultimap.of()
    }

    return _cache.get(paramSet.ID, \-> {
      var builder = ImmutableListMultimap.builder<CalcRoutineParamName, InScopeUsage>();
      Arrays.stream(paramSet.Parameters)
          .forEach(\param -> {
            var tempList : List<InScopeUsage>
            if (param.isCoverageParam and param.CoveragePattern != null) {
              tempList = getCoverageElements(param)
            } else {
              var rootType = rootType(param)
              tempList = getTargetableElements(param.Code.Code, rootType)
              tempList.addAll(getInScopeModifiers(param, rootType, paramSet.PolicyLinePatternCode))
            }
            tempList = InScopeUsageFilter.filterIrrelevantItems(tempList, paramSet.PolicyLinePatternCode)
            builder.putAll(param.Code, tempList)
          })
      return builder.build()
    })
  }

  /**
   * @return a map of all usages for each parameter in the parameter set that are targetable in a routine.
   */
  function getTargetableElementsForParameterSet(paramSet : CalcRoutineParameterSet) : Map<CalcRoutineParamName, List<InScopeUsage>> {
    var mapFromParamToUsages = getImmutableTargetableElementsForParameterSet(paramSet)

    // create a new map, containing copy of the values lists.
    var resultMap = Maps.newHashMapWithExpectedSize<CalcRoutineParamName, List<InScopeUsage>>(mapFromParamToUsages.size())
    for(key in mapFromParamToUsages.keys()) {
      resultMap.put(key, mapFromParamToUsages.get(key).copy())
    }
    return resultMap
  }

  private function rootType(param : CalcRoutineParameter) : IType {
    return param.UseWrapper
        ? gw.lang.reflect.TypeSystem.getByRelativeName(param.WrapperClass)
        : gw.lang.reflect.TypeSystem.getByRelativeName(param.ParamType)
  }

  /**
   * @return an immutable map of all usages for each parameter in the parameter set that are targetable in a routine.
   */
  function getImmutableTargetableElementsForWritableParameterSet(paramSet : CalcRoutineParameterSet) : ImmutableListMultimap<CalcRoutineParamName, InScopeUsage> {
    if (paramSet == null) {
      return ImmutableListMultimap.of()
    }

    return _writableUsageMapCache.get(paramSet.ID, \-> {
      var builder = ImmutableListMultimap.builder<CalcRoutineParamName, InScopeUsage>();
      Arrays.stream(paramSet.Parameters)
          .filter(\param -> param.Writable and !(param.isCoverageParam and param.CoveragePattern != null))
          .forEach(\param -> {
            var tempList = getTargetableWritableElements(param.Code.Code, rootType(param))
                .collect(Collectors.toList<InScopeUsage>())
            tempList = InScopeUsageFilter.filterIrrelevantItems(tempList, paramSet.PolicyLinePatternCode)
            builder.putAll(param.Code, tempList)
          })
      return builder.build()
    })
  }

  /**
   * @return a map of all usages for each parameter in the parameter set that are targetable in a routine.
   */
  function getTargetableElementsForWritableParameterSet(paramSet : CalcRoutineParameterSet) : Map<CalcRoutineParamName, List<InScopeUsage>> {
    var mapFromParamToUsages = getImmutableTargetableElementsForWritableParameterSet(paramSet)

    // create a new map, containing copy of the values lists.
    var resultMap = Maps.newHashMapWithExpectedSize<CalcRoutineParamName, List<InScopeUsage>>(mapFromParamToUsages.size())
    for(key in mapFromParamToUsages.keys()) {
      resultMap.put(key, mapFromParamToUsages.get(key).copy())
    }
    return resultMap
  }

  public static function isCacheForParamSetStale(paramSet : CalcRoutineParameterSet) : boolean {
    var allRootsParams = paramSet.Parameters*.DisplayName.toSet()
    var usageMap = _cache.getIfPresent(paramSet.ID)
    if (usageMap != null &&
        usageMap.keys().stream()
            .map(\k -> k.toString())
            .anyMatch(\k -> !allRootsParams.contains(k))){
      return true
    }
    var writableUsageMap = _writableUsageMapCache.getIfPresent(paramSet.ID)
    if (writableUsageMap != null &&
        writableUsageMap.keys().stream()
            .map(\ k -> k.toString())
            .anyMatch(\k -> !allRootsParams.contains(k))){
      return true
    }
    return false
  }

  public static function updateCacheWithParamSet(regt : RatingEntityGraphTraverser, paramSet : CalcRoutineParameterSet) {
    _cache.invalidate(paramSet.ID)
    regt.getImmutableTargetableElementsForParameterSet(paramSet)
    _writableUsageMapCache.invalidate(paramSet.ID)
    regt.getImmutableTargetableElementsForWritableParameterSet(paramSet)
  }

  /**
   * Returns a map of usages that are targetable from the given paramSet and restricted to the set of restrictToTypes.
   */
  function getRestrictedTargetableElementsForParameter(paramSet : CalcRoutineParameterSet, restrictToTypes : Set<IType>) : Map<CalcRoutineParamName, List<InScopeUsage>> {
    if (paramSet == null) {
      return {}
    }

    var usagesMap = getTargetableElementsForParameterSet(paramSet)
    usagesMap.eachValue(\ list -> list.removeWhere(\u -> not TypeMaps.isAssignable(restrictToTypes, u.FeatureType)))
    return usagesMap
  }

  /**
   * For a given root name, finds all elements that can be used.
   */
  function getTargetableElements(rootName : String, rootType : IType) : List<InScopeUsage> {
    // local variables declared for the use of the recursive block below
    var usages : List<InScopeUsage> = {}
    final var seen : Set<IType> = {}

    var traverseTypeTreeBlock : block(prefix : String, type : IType)
    traverseTypeTreeBlock = \ prefix, type -> {
      var previouslyUnseen = seen.add(type)
      if (previouslyUnseen) {
        _rfLogger.debug("getTargetableElements() - already seen type %s: Adding", type.toString())
      } else {
        _rfLogger.debug("getTargetableElements() - already seen type %s: returning", type.toString())
        return
      }
      for (p in type.TypeInfo.Properties){
        if (filterReadableElts(p)){
          usages.add(getInScopeUsage(rootName, addPrefix(prefix, p.Name), p.FeatureType, p.Writable))
          if (isNavigable(type, p)){
            traverseTypeTreeBlock(addPrefix(prefix, p.Name), p.FeatureType)
          }
        }
      }
      seen.remove(type)
    }

    usages.add(getInScopeUsage(rootName, null, rootType, false))
    if (traverseProperties(rootType)) {
      traverseTypeTreeBlock(null, rootType)
    }
    return usages
  }

  function filterReadableElts(propInfo : IPropertyInfo): boolean {
    return !exclude(_policyLine, propInfo) &&
        propInfo.Readable &&
        !propInfo.Hidden &&
        !propInfo.Internal
  }

  function filterWritableElts(propInfo : IPropertyInfo): boolean {
    //Filter entities and entity properties from traversal
    return (not (propInfo.OwnersType typeis IEntityType))
        && (not (propInfo.FeatureType typeis IEntityType))
        && filterReadableElts(propInfo)
  }

  /**
   * For a given root name, finds all elements that can be used.
   */
  private function getTargetableWritableElements(rootName : String, rootType : IType) : Stream<InScopeUsage> {
    // local variables declared for the use of the recursive block below
    var seen : Set<IType> = {}

    var traverseTypeTreeBlock : block(prefix : String, type : IType) : Stream<InScopeUsage>
    traverseTypeTreeBlock = \ prefix, type -> {

      if (seen.add(type)) {
        _rfLogger.debug("getTargetableWritableElements - already seen type %s: Adding", type.toString())
      } else {
        _rfLogger.debug("getTargetableWritableElements - already seen type %s: returning", type.toString())
        return Stream.empty()
      }
      var usagesStream = Stream.empty<InScopeUsage>();
      for (p in type.TypeInfo.Properties){
        if (filterWritableElts(p)){
          if (p.Writable){
            var streamOfField = Stream.of(InScopeUsage.makeUsageForEntityField(rootName, addPrefix(prefix, p.Name), p.FeatureType, false, p.Writable))
            usagesStream = Stream.concat(usagesStream, streamOfField)
          }
          if (isNavigable(type, p)){
            var visitTreeStream = traverseTypeTreeBlock(addPrefix(prefix, p.Name), p.FeatureType)
            usagesStream = Stream.concat(usagesStream, visitTreeStream)
          }
        }
      }
      seen.remove(type)
      return usagesStream
    }

    if (traverseProperties(rootType)) {
      if (rootType typeis IEntityType){
        //If we encounter an entity, visit it's immediate properites.
        // don't visit further down the graph for writable fields.
        //Only visit a graph for non-entity fields.
        return collectEntityWritableProperties(rootName, null, rootType)
            .sorted(Comparator.comparing<InScopeUsage, String>(\i -> (i as InScopeUsage).DisplayName))
      } else {
        var stream =  rootType.TypeInfo.Properties.stream()
            .filter(\p -> not exclude(_policyLine, p))
            .flatMap<InScopeUsage>(\p -> {
              var featureStream = Stream.empty<InScopeUsage>()
              if (p.Writable) {
                var feature = InScopeUsage.makeUsageForEntityField(rootName, addPrefix(null, p.Name), p.FeatureType, false, p.Writable)
                featureStream = Stream.of(feature)
              }

              return Stream.concat<InScopeUsage>(featureStream, traverseTypeTreeBlock(null, rootType))
            })

         return stream
            .sorted(Comparator.comparing<InScopeUsage, String>(\i -> (i as InScopeUsage).DisplayName))
      }
    }
    return Stream.empty()
  }

  private function collectEntityWritableProperties(rootName : String, prefix : String, entityType : IEntityType) : Stream<InScopeUsage> {
    return entityType.TypeInfo.Properties.stream()
      .filter(\p -> p.Writable && not exclude(_policyLine, p))  //ToDo: this should also hide internal/hidden elements
      .map(\p -> InScopeUsage.makeUsageForEntityField(rootName, addPrefix(prefix, p.Name), p.FeatureType, false, p.Writable))
  }

  private function addPrefix(prefix : String, name : String) : String {
    return prefix == null ? name : "${prefix}.${name}"
  }

  private function getCoverageElements(param : entity.CalcRoutineParameter) : List<InScopeUsage> {
    var usages : List<InScopeUsage> = {}
    var pattern = PolicyLinePatternLookup.getByPublicID(param.CalcRoutineParameterSet.PolicyLinePatternCode)
    var coverage = pattern.getCoveragePatternByPublicId(param.CoveragePattern)
    coverage.CovTerms.forEach( \ covTerm -> {
      usages.add(InScopeUsage.makeUsageForCoverageCovTerm(param.Code.Code, covTerm))
      usages.add(InScopeUsage.makeUsageForCoverageTermValue(param.Code.Code, covTerm))

      if (covTerm typeis OptionCovTermPattern) {
        // Options are normally matched as String, but there is a Numeric value associated with them also.
        usages.add(InScopeUsage.makeUsageForOptionCovTermNumericValue(param.Code.Code, covTerm))
      }
    })
    var typeName = param.UseWrapper ? param.WrapperClass : param.ParamType
    usages.add(InScopeUsage.makeUsageForCoverage(param.Code.Code, null, TypeSystem.getByFullName(typeName)))
    return usages
  }

  private function getInScopeModifiers(param : CalcRoutineParameter, rootType : IType, policyLinePatternCode : String) : List<InScopeUsage> {
        //Add modifiers
    if (rootType typeis IEntityType && rootType.Interfaces.contains(Modifiable)) {
      var calcRoutineParamCode = param.Code.Code
      return getModifiersForModifiable(policyLinePatternCode, rootType)
          .map(\mp -> InScopeUsage.makeUsageForModifierPattern(calcRoutineParamCode, mp))
          .collect(Collectors.toList<InScopeUsage>())
    }
    return ImmutableList.of<InScopeUsage>()
  }

  private function getInScopeUsage(rootName : String, path : String, targetType : IType, isWritable : boolean) : InScopeUsage {
    var inScopeUsg : InScopeUsage
    if (gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(Coverage.Type, targetType)) {
      inScopeUsg = InScopeUsage.makeUsageForCoverage(rootName, path, targetType)
    } else {
      inScopeUsg = InScopeUsage.makeUsageForEntityField(rootName, path, targetType, targetType.Interfaces.contains(Modifier), isWritable)
    }
    return inScopeUsg
  }

  private static function getModifiersForModifiable(policyLinePatternCode : String, modifiable : IEntityType) : Stream<ModifierPattern> {
    var linePatternStream = PolicyLinePatternLookup.All.stream()
    if (policyLinePatternCode != null){
      linePatternStream = linePatternStream.filter(\p -> p.PublicID == policyLinePatternCode)
    }
    return linePatternStream
        .flatMap<ModifierPattern>(\ linePattern -> linePattern.ModifierPatterns.stream())
        .filter(\ mp -> mp.OwningEntityType == modifiable)
  }

  private function memberOfRootEntities(t : IType) : boolean {
    return _allRoots.stream().anyMatch( \ r -> gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(t, r) or gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(r, t))
  }

  private function isNavigable(owner: IType, p : IPropertyInfo) : boolean {
   return isNavigable(owner, p, false)
  }

  /**
   * Returns true if the property should also be processed for sub properties. It excludes types where the property is
   * contained in the set of root entities to keep the list of options small.
   */
  private function isNavigable(owner: IType, p : IPropertyInfo, entitiesOnly : boolean) : boolean {
    //Find the feature type regardless of whether it is an array or not
    var propType = p.FeatureType.Array ? p.FeatureType.ComponentType : p.FeatureType

    // these qualities take highest precedence
    var meetsMinimumCriteria = p.Readable and not (p.Hidden or p.Internal)
                and (propType typeis IEntityType or not entitiesOnly)
                and  not p.FeatureType.Array
                //check if the property belongs to the owner entity, as opposed to being accessed by enhancement from somewhere else
                and (gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(owner, p.OwnersType) or (owner typeis IEntityType and p.OwnersType typeis IEntityType))
                and not InScopeUsageFilter.treatTypeAsPrimitive(p.FeatureType)

    if (not meetsMinimumCriteria) {
      return false
    }

    // Can't do this via RateRoutineConfig because visibility annotation takes precedence
    if (p.Name == "Parent" || p.Name == "AsDataModel") {
      return false
    }

    // second precedence goes to an explicit @VisibleInRateflow annotation...
    var anntn = visibilityAnnotation(p)
    if (anntn != null) {
      return anntn
    }

    // ...and similarly, InScopeUsageFilter's advisement
    var includeProperty = InScopeUsageFilter.includeProperty(_policyLine, p)
    if (includeProperty != null) {
      return includeProperty
    }

    // _filter.get().exclude() filters out many of the system "infrastructure" properties, e.g. BasedOn, EffectiveDate, Branch....
    //
    // we don't want to navigate into variables where the type is the same as another parameter.
    // PC-18535 discusses the issue further.
    if (exclude(_policyLine, p) or memberOfRootEntities(p.FeatureType)) {
      return false
    }

    // don't traverse answer delegates
    if ((propType typeis IEntityType)
        and (propType.RootType.Interfaces.hasMatch(\ iFace -> iFace == entity.PCAnswerDelegate)) ) {
      return false
    }

    // finally, traverse anything that is EffDated or SimpleEffDated
    var allTypesInHierarchy = propType.AllTypesInHierarchy
    return     allTypesInHierarchy.contains(entity.EffDated)
            or allTypesInHierarchy.contains(entity.SimpleEffDated) // mostly ClassCodes
  }

  private static function visibilityAnnotation(p : IPropertyInfo) : Boolean {
    var annotations = p.getAnnotationsOfType(VisibleInRateflow)
    var values = annotations.map(\ a -> (a.Instance as VisibleInRateflow).visible()).toSet()

    // if annotation == null, annotation.Instance == null and typeis check will fail
    if (values.size() == 1) {
      return values.single()
    } else if (values.size() > 1) {
      _rfLogger.warn("Ignoring VisibleInRateflow on property ${p.Name} because it has multiple annotations with conflicting values")
    }

    return null
  }

  /**
   * Filter that removes certain common and system properties.
   */
  static class TypeUsageFilter {
    final var ignoredProperties : HashSet<String> =
      {"BasedOnUntyped", "OriginalVersion", "UnslicedUntyped", "Bean", "PublicID", "BasedOn", "CoverageSymbol", "CoverageSymbolGroup",
       "BeanVersion", "CreateUser", "UpdateUser", "CreateTime", "UpdateTime", "Costs", "HasElements", "Count", "IsEmpty", "Subtype", "Unsliced", "Class"}

    function traverseProperties(targetType: IType) : boolean {
      if (gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(TypeKey.Type, targetType)) {
        return false
      } else {
        return true
      }
    }

    /**
     * Excludes system and low-level properties from the list of targetable elements.
     */
    function exclude(policyLine : String, p : IPropertyInfo): boolean {
      // highest precedence goes to an explicit @VisibleInRateflow annotation...
      var anntn = visibilityAnnotation(p)
      if (anntn != null) {
        return not anntn
      }

      // ...and similarly, InScopeUsageFilter's advisement
      var includeProperty = InScopeUsageFilter.includeProperty(policyLine, p)
      if (includeProperty != null) {
        return not includeProperty
      }

      // Do not traverse Coverage entities because we handle them separately
      var allTypesInHierarchy = p.OwnersType.AllTypesInHierarchy
      if (allTypesInHierarchy.hasMatch(\i -> i.Name == "gw.lang.reflect.IDynamicallyTypedEntity")
      and allTypesInHierarchy.hasMatch(\i -> i.Name == "entity.Coverage")) {
        if (_rfLogger.DebugEnabled) {
          _rfLogger.debug("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to IDynamicallyTypedEntity and entity.Coverage")
        }
        return true
      }
      if (p.OwnersType.Name.startsWith("com.guidewire")) {
        if (_rfLogger.DebugEnabled) {
          _rfLogger.debug("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to com.guidewire")
        }
        return true
      }
      if (p.OwnersType.Name.startsWith("entity.EffDated")) {
        if (_rfLogger.TraceEnabled) {
          _rfLogger.trace("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to EffDated")
        }
        return true
      }
      if (p.OwnersType.Name == "gw.util.EffDatedEnhancement") {
        if (_rfLogger.TraceEnabled) {
          _rfLogger.trace("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to EffDatedEnhancement")
        }
        return true
      }
      if (p.OwnersType.Name == "entity.KeyableBean") {
        if (_rfLogger.TraceEnabled) {
          _rfLogger.trace("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to KeyableBean")
        }
        return true
      }
      if (p.FeatureType.Name.startsWith("entity.windowed")) {
        if (_rfLogger.TraceEnabled) {
          _rfLogger.trace("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to entity.windowed")
        }
        return true
      }
      if ( p.FeatureType.Name.startsWith("com.guidewire.commons")) {
        if (_rfLogger.TraceEnabled) {
          _rfLogger.trace("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to com.guidewire.commons")
        }
        return true
      }
      if (p.FeatureType.Name.startsWith("gw.lang.reflect")) {
        if (_rfLogger.TraceEnabled) {
          _rfLogger.trace("Excluding property: ${p.DisplayName} where p.OwnersType=${p.OwnersType.Name} due to gw.lang.reflect")
        }
        return true
      }
      if (ignoredProperties.contains(p.Name)) {
        if (_rfLogger.TraceEnabled) {
          _rfLogger.trace("Excluding property: ${p.DisplayName} where p.Name=${p.Name} due to ignoredProperties")
        }
        return true
      }
      if (_rfLogger.TraceEnabled) {
        _rfLogger.trace("Including property: ${p.DisplayName} where p.Name = ${p.Name} and p.OwnersType={p.OwnersType.Name}")
      }
      return false
    }
  }
}
