/**
   * Hibernate entities might be dirty (their state has changed), but none of these changes would
   * affect the index state. This method will return {@code true} if any of changed entity
   * properties identified by their names ({@code dirtyPropertyNames}) will effect the index state.
   *
   * @param dirtyPropertyNames array of property names for the changed entity properties, {@code
   *     null} in case the changed properties cannot be specified.
   * @return {@code true} if the entity changes will effect the index state, {@code false} otherwise
   * @since 3.4
   */
  public boolean isDirty(String[] dirtyPropertyNames) {
    if (dirtyPropertyNames == null || dirtyPropertyNames.length == 0) {
      return true; // it appears some collection work has no oldState -> reindex
    }
    if (!stateInspectionOptimizationsEnabled()) {
      return true;
    }

    for (String dirtyPropertyName : dirtyPropertyNames) {
      PropertyMetadata propertyMetadata =
          typeMetadata.getPropertyMetadataForProperty(dirtyPropertyName);
      if (propertyMetadata != null) {
        // if there is a property metadata it means that there is at least one @Field.
        // Fields are either indexed or stored, so we need to re-index
        return true;
      }

      // consider IndexedEmbedded:
      for (EmbeddedTypeMetadata embeddedTypeMetadata : typeMetadata.getEmbeddedTypeMetadata()) {
        String name = embeddedTypeMetadata.getEmbeddedFieldName();
        if (name.equals(dirtyPropertyName)) {
          return true;
        }
      }
    }
    return false;
  }
  /**
   * If we have a work instance we have to check whether the instance to be indexed is contained in
   * any other indexed entities for a tenant.
   *
   * @param instance the instance to be indexed
   * @param workPlan the current work plan
   * @param currentRecursionContext the current {@link
   *     org.hibernate.search.engine.spi.ContainedInRecursionContext} object used to check the graph
   *     traversal
   * @param tenantIdentifier the identifier of the tenant or null, if there isn't one
   * @see #appendContainedInWorkForInstance(Object, WorkPlan, ContainedInRecursionContext)
   */
  public void appendContainedInWorkForInstance(
      Object instance,
      WorkPlan workPlan,
      ContainedInRecursionContext currentRecursionContext,
      String tenantIdentifier) {
    for (ContainedInMetadata containedInMetadata : typeMetadata.getContainedInMetadata()) {
      XMember member = containedInMetadata.getContainedInMember();
      Object unproxiedInstance = instanceInitializer.unproxy(instance);

      ContainedInRecursionContext recursionContext =
          updateContainedInRecursionContext(
              unproxiedInstance, containedInMetadata, currentRecursionContext);

      if (recursionContext.isTerminal()) {
        continue;
      }

      Object value = ReflectionHelper.getMemberValue(unproxiedInstance, member);

      if (value == null) {
        continue;
      }

      if (member.isArray()) {
        Object[] array = (Object[]) value;
        for (Object arrayValue : array) {
          processSingleContainedInInstance(
              workPlan, arrayValue, recursionContext, tenantIdentifier);
        }
      } else if (member.isCollection()) {
        Collection<?> collection = null;
        try {
          collection = getActualCollection(member, value);
          collection.size(); // load it
        } catch (Exception e) {
          if (e.getClass().getName().contains("org.hibernate.LazyInitializationException")) {
            /* A deleted entity not having its collection initialized
             * leads to a LIE because the collection is no longer attached to the session
             *
             * But that's ok as the collection update event has been processed before
             * or the fk would have been cleared and thus triggering the cleaning
             */
            collection = null;
          }
        }
        if (collection != null) {
          for (Object collectionValue : collection) {
            processSingleContainedInInstance(
                workPlan, collectionValue, recursionContext, tenantIdentifier);
          }
        }
      } else {
        processSingleContainedInInstance(workPlan, value, recursionContext, tenantIdentifier);
      }
    }
  }
 /**
  * Verifies entity level preconditions to know if it's safe to skip index updates based on
  * specific field or collection updates.
  *
  * @return true if it seems safe to apply such optimizations
  */
 boolean stateInspectionOptimizationsEnabled() {
   if (!typeMetadata.areStateInspectionOptimizationsEnabled()) {
     return false;
   }
   if (typeMetadata.areClassBridgesUsed()) {
     log.tracef(
         "State inspection optimization disabled as entity %s uses class bridges",
         this.beanClass.getName());
     return false; // can't know what a class bridge is going to look at -> reindex // TODO nice
     // new feature to have?
   }
   BoostStrategy boostStrategy = typeMetadata.getDynamicBoost();
   if (boostStrategy != null && !(boostStrategy instanceof DefaultBoostStrategy)) {
     log.tracef(
         "State inspection optimization disabled as DynamicBoost is enabled on entity %s",
         this.beanClass.getName());
     return false; // as with class bridge: might be affected by any field // TODO nice new feature
     // to have?
   }
   return true;
 }
  /**
   * Returns {@code true} if the collection event is going to affect the index state. In this case
   * the indexing event can be ignored. {@code false} otherwise.
   *
   * @param collectionRoleName a {@link java.lang.String} object.
   * @return {@code true} if an update to the collection identified by the given role name effects
   *     the index state, {@code false} otherwise.
   */
  public boolean collectionChangeRequiresIndexUpdate(String collectionRoleName) {
    if (collectionRoleName == null) {
      // collection name will only be non null for PostCollectionUpdateEvents
      return true;
    }

    // don't check stateInspectionOptimizationsEnabled() as it might ignore depth limit:
    // it will disable optimization even if we have class bridges, but we're too deep
    // to be reachable. The evaluation of stateInspectionOptimizationsEnabled() was
    // actually stored in stateInspectionOptimizationsEnabled, but limiting to depth recursion.
    if (!typeMetadata.areStateInspectionOptimizationsEnabled()) {
      // if optimizations are not enabled we need to re-index
      return true;
    }

    return this.typeMetadata.containsCollectionRole(collectionRoleName);
  }
  /**
   * Constructor.
   *
   * @param xClass The class for which to build a document builder
   * @param typeMetadata metadata for the specified class
   * @param reflectionManager Reflection manager to use for processing the annotations
   * @param optimizationBlackList keeps track of types on which we need to disable collection events
   *     optimizations
   * @param instanceInitializer a {@link org.hibernate.search.spi.InstanceInitializer} object.
   */
  public AbstractDocumentBuilder(
      XClass xClass,
      TypeMetadata typeMetadata,
      ReflectionManager reflectionManager,
      Set<XClass> optimizationBlackList,
      InstanceInitializer instanceInitializer) {
    if (xClass == null) {
      throw new AssertionFailure(
          "Unable to build a DocumentBuilderContainedEntity with a null class");
    }

    this.instanceInitializer = instanceInitializer;
    this.entityState = EntityState.CONTAINED_IN_ONLY;
    this.beanXClass = xClass;
    this.beanClass = reflectionManager.toClass(xClass);
    this.typeMetadata = typeMetadata;

    optimizationBlackList.addAll(typeMetadata.getOptimizationBlackList());
  }
 /** Closes any resource */
 public void close() {
   typeMetadata.getDefaultAnalyzerReference().close();
 }
 /**
  * Makes sure isCollectionRoleExcluded will always return false, so that collection update events
  * are always processed.
  *
  * @see #collectionChangeRequiresIndexUpdate(String)
  */
 public void forceStateInspectionOptimizationsDisabled() {
   typeMetadata.disableStateInspectionOptimizations();
 }
 public ScopedAnalyzerReference getAnalyzerReference() {
   return typeMetadata.getDefaultAnalyzerReference();
 }