/**
  * Recursively constructs sets of audited and not audited properties and classes which behavior
  * has been overridden using {@link AuditOverride} annotation.
  *
  * @param clazz Class that is being processed. Currently mapped entity shall be passed during
  *     first invocation.
  */
 private void readAuditOverrides(XClass clazz) {
   /* TODO: Code to remove with @Audited.auditParents - start. */
   final Audited allClassAudited = clazz.getAnnotation(Audited.class);
   if (allClassAudited != null && allClassAudited.auditParents().length > 0) {
     for (Class c : allClassAudited.auditParents()) {
       final XClass parentClass = reflectionManager.toXClass(c);
       checkSuperclass(clazz, parentClass);
       if (!overriddenNotAuditedClasses.contains(parentClass)) {
         // If the class has not been marked as not audited by the subclass.
         overriddenAuditedClasses.add(parentClass);
       }
     }
   }
   /* TODO: Code to remove with @Audited.auditParents - finish. */
   final List<AuditOverride> auditOverrides = computeAuditOverrides(clazz);
   for (AuditOverride auditOverride : auditOverrides) {
     if (auditOverride.forClass() != void.class) {
       final XClass overrideClass = reflectionManager.toXClass(auditOverride.forClass());
       checkSuperclass(clazz, overrideClass);
       final String propertyName = auditOverride.name();
       if (!StringTools.isEmpty(propertyName)) {
         // Override @Audited annotation on property level.
         final XProperty property = getProperty(overrideClass, propertyName);
         if (auditOverride.isAudited()) {
           if (!overriddenNotAuditedProperties.contains(property)) {
             // If the property has not been marked as not audited by the subclass.
             overriddenAuditedProperties.add(property);
           }
         } else {
           if (!overriddenAuditedProperties.contains(property)) {
             // If the property has not been marked as audited by the subclass.
             overriddenNotAuditedProperties.add(property);
           }
         }
       } else {
         // Override @Audited annotation on class level.
         if (auditOverride.isAudited()) {
           if (!overriddenNotAuditedClasses.contains(overrideClass)) {
             // If the class has not been marked as not audited by the subclass.
             overriddenAuditedClasses.add(overrideClass);
           }
         } else {
           if (!overriddenAuditedClasses.contains(overrideClass)) {
             // If the class has not been marked as audited by the subclass.
             overriddenNotAuditedClasses.add(overrideClass);
           }
         }
       }
     }
   }
   final XClass superclass = clazz.getSuperclass();
   if (!clazz.isInterface() && !Object.class.getName().equals(superclass.getName())) {
     readAuditOverrides(superclass);
   }
 }
  public ClassAuditingData getAuditData() {
    if (pc.getClassName() == null) {
      return auditData;
    }

    try {
      XClass xclass = reflectionManager.classForName(pc.getClassName(), this.getClass());

      ModificationStore defaultStore = getDefaultAudited(xclass);
      if (defaultStore != null) {
        auditData.setDefaultAudited(true);
      }

      new AuditedPropertiesReader(
              defaultStore,
              new PersistentClassPropertiesSource(xclass),
              auditData,
              globalCfg,
              reflectionManager,
              "")
          .read();

      addAuditTable(xclass);
      addAuditSecondaryTables(xclass);
    } catch (ClassNotFoundException e) {
      throw new MappingException(e);
    }

    return auditData;
  }
    public ComponentPropertiesSource(ReflectionManager reflectionManager, Component component) {
      try {
        this.xclass =
            reflectionManager.classForName(component.getComponentClassName(), this.getClass());
      } catch (ClassNotFoundException e) {
        throw new MappingException(e);
      }

      this.component = component;
    }
 private ContainerType getContainerType(XMember member, ReflectionManager reflectionManager) {
   if (!member.isAnnotationPresent(IndexedEmbedded.class)) {
     return ContainerType.SINGLE;
   }
   if (member.isArray()) {
     return ContainerType.ARRAY;
   }
   Class<?> typeClass = reflectionManager.toClass(member.getType());
   if (Iterable.class.isAssignableFrom(typeClass)) {
     return ContainerType.ITERABLE;
   }
   if (member.isCollection() && Map.class.equals(member.getCollectionClass())) {
     return ContainerType.MAP;
   }
   // marked @IndexedEmbedded but not a container
   // => probably a @Field @IndexedEmbedded Foo foo;
   return ContainerType.SINGLE;
 }
  /**
   * 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());
  }
  /**
   * This extracts and instantiates the implementation class from a ClassBridge annotation.
   *
   * @param fieldBridgeAnnotation the FieldBridge annotation
   * @param appliedOnType the type the bridge is applied on
   * @param reflectionManager The reflection manager instance
   * @return FieldBridge
   */
  private FieldBridge extractType(
      org.hibernate.search.annotations.FieldBridge fieldBridgeAnnotation,
      XClass appliedOnType,
      ReflectionManager reflectionManager) {
    FieldBridge bridge = null;

    if (fieldBridgeAnnotation != null) {
      bridge =
          doExtractType(
              fieldBridgeAnnotation,
              appliedOnType.getName(),
              reflectionManager.toClass(appliedOnType));
    }

    if (bridge == null) {
      throw LOG.unableToDetermineClassBridge(appliedOnType.getName());
    }

    return bridge;
  }
 private FieldBridge doExtractType(
     org.hibernate.search.annotations.FieldBridge bridgeAnn,
     XMember member,
     ReflectionManager reflectionManager) {
   return doExtractType(bridgeAnn, member.getName(), reflectionManager.toClass(member.getType()));
 }
  public static Callback[] resolveCallback(
      XClass beanClass, Class annotation, ReflectionManager reflectionManager) {
    List<Callback> callbacks = new ArrayList<Callback>();
    List<String> callbacksMethodNames = new ArrayList<String>(); // used to track overridden methods
    List<Class> orderedListeners = new ArrayList<Class>();
    XClass currentClazz = beanClass;
    boolean stopListeners = false;
    boolean stopDefaultListeners = false;
    do {
      Callback callback = null;
      List<XMethod> methods = currentClazz.getDeclaredMethods();
      final int size = methods.size();
      for (int i = 0; i < size; i++) {
        final XMethod xMethod = methods.get(i);
        if (xMethod.isAnnotationPresent(annotation)) {
          Method method = reflectionManager.toMethod(xMethod);
          final String methodName = method.getName();
          if (!callbacksMethodNames.contains(methodName)) {
            // overridden method, remove the superclass overridden method
            if (callback == null) {
              callback = new BeanCallback(method);
              Class returnType = method.getReturnType();
              Class[] args = method.getParameterTypes();
              if (returnType != Void.TYPE || args.length != 0) {
                throw new RuntimeException(
                    "Callback methods annotated on the bean class must return void and take no arguments: "
                        + annotation.getName()
                        + " - "
                        + xMethod);
              }
              if (!method.isAccessible()) method.setAccessible(true);
              LOG.debugf(
                  "Adding %s as %s callback for entity %s",
                  methodName, annotation.getSimpleName(), beanClass.getName());
              callbacks.add(0, callback); // superclass first
              callbacksMethodNames.add(0, methodName);
            } else {
              throw new PersistenceException(
                  "You can only annotate one callback method with "
                      + annotation.getName()
                      + " in bean class: "
                      + beanClass.getName());
            }
          }
        }
      }
      if (!stopListeners) {
        getListeners(currentClazz, orderedListeners);
        stopListeners = currentClazz.isAnnotationPresent(ExcludeSuperclassListeners.class);
        stopDefaultListeners = currentClazz.isAnnotationPresent(ExcludeDefaultListeners.class);
      }

      do {
        currentClazz = currentClazz.getSuperclass();
      } while (currentClazz != null
          && !(currentClazz.isAnnotationPresent(Entity.class)
              || currentClazz.isAnnotationPresent(MappedSuperclass.class)));
    } while (currentClazz != null);

    // handle default listeners
    if (!stopDefaultListeners) {
      List<Class> defaultListeners =
          (List<Class>) reflectionManager.getDefaults().get(EntityListeners.class);

      if (defaultListeners != null) {
        int defaultListenerSize = defaultListeners.size();
        for (int i = defaultListenerSize - 1; i >= 0; i--) {
          orderedListeners.add(defaultListeners.get(i));
        }
      }
    }

    for (Class listener : orderedListeners) {
      Callback callback = null;
      if (listener != null) {
        XClass xListener = reflectionManager.toXClass(listener);
        callbacksMethodNames = new ArrayList<String>();
        List<XMethod> methods = xListener.getDeclaredMethods();
        final int size = methods.size();
        for (int i = 0; i < size; i++) {
          final XMethod xMethod = methods.get(i);
          if (xMethod.isAnnotationPresent(annotation)) {
            final Method method = reflectionManager.toMethod(xMethod);
            final String methodName = method.getName();
            if (!callbacksMethodNames.contains(methodName)) {
              // overridden method, remove the superclass overridden method
              if (callback == null) {
                try {
                  callback = new ListenerCallback(method, listener.newInstance());
                } catch (IllegalAccessException e) {
                  throw new PersistenceException(
                      "Unable to create instance of "
                          + listener.getName()
                          + " as a listener of beanClass",
                      e);
                } catch (InstantiationException e) {
                  throw new PersistenceException(
                      "Unable to create instance of "
                          + listener.getName()
                          + " as a listener of beanClass",
                      e);
                }
                Class returnType = method.getReturnType();
                Class[] args = method.getParameterTypes();
                if (returnType != Void.TYPE || args.length != 1) {
                  throw new PersistenceException(
                      "Callback methods annotated in a listener bean class must return void and take one argument: "
                          + annotation.getName()
                          + " - "
                          + method);
                }
                if (!method.isAccessible()) method.setAccessible(true);
                LOG.debugf(
                    "Adding %s as %s callback for entity %s",
                    methodName, annotation.getSimpleName(), beanClass.getName());
                callbacks.add(0, callback); // listeners first
              } else {
                throw new PersistenceException(
                    "You can only annotate one callback method with "
                        + annotation.getName()
                        + " in bean class: "
                        + beanClass.getName()
                        + " and callback listener: "
                        + listener.getName());
              }
            }
          }
        }
      }
    }
    return callbacks.toArray(new Callback[callbacks.size()]);
  }
  public RevisionInfoConfigurationResult configure(
      Configuration cfg, ReflectionManager reflectionManager) {
    Iterator<PersistentClass> classes = (Iterator<PersistentClass>) cfg.getClassMappings();
    boolean revisionEntityFound = false;
    RevisionInfoGenerator revisionInfoGenerator = null;

    Class<?> revisionInfoClass = null;

    while (classes.hasNext()) {
      PersistentClass pc = classes.next();
      XClass clazz;
      try {
        clazz = reflectionManager.classForName(pc.getClassName(), this.getClass());
      } catch (ClassNotFoundException e) {
        throw new MappingException(e);
      }

      RevisionEntity revisionEntity = clazz.getAnnotation(RevisionEntity.class);
      if (revisionEntity != null) {
        if (revisionEntityFound) {
          throw new MappingException("Only one entity may be annotated with @RevisionEntity!");
        }

        // Checking if custom revision entity isn't audited
        if (clazz.getAnnotation(Audited.class) != null) {
          throw new MappingException("An entity annotated with @RevisionEntity cannot be audited!");
        }

        revisionEntityFound = true;

        MutableBoolean revisionNumberFound = new MutableBoolean();
        MutableBoolean revisionTimestampFound = new MutableBoolean();
        MutableBoolean modifiedEntityNamesFound = new MutableBoolean();

        searchForRevisionInfoCfg(
            clazz,
            reflectionManager,
            revisionNumberFound,
            revisionTimestampFound,
            modifiedEntityNamesFound);

        if (!revisionNumberFound.isSet()) {
          throw new MappingException(
              "An entity annotated with @RevisionEntity must have a field annotated "
                  + "with @RevisionNumber!");
        }

        if (!revisionTimestampFound.isSet()) {
          throw new MappingException(
              "An entity annotated with @RevisionEntity must have a field annotated "
                  + "with @RevisionTimestamp!");
        }

        revisionInfoEntityName = pc.getEntityName();
        revisionInfoClass = pc.getMappedClass();
        Class<? extends RevisionListener> revisionListenerClass =
            getRevisionListenerClass(revisionEntity.value());
        revisionInfoTimestampType = pc.getProperty(revisionInfoTimestampData.getName()).getType();
        if (globalCfg.isTrackEntitiesChangedInRevisionEnabled()
            || DefaultTrackingModifiedEntitiesRevisionEntity.class.isAssignableFrom(
                revisionInfoClass)
            || modifiedEntityNamesFound.isSet()) {
          // If tracking modified entities parameter is enabled, custom revision info entity is a
          // subtype
          // of DefaultTrackingModifiedEntitiesRevisionEntity class, or @ModifiedEntityNames
          // annotation is used.
          revisionInfoGenerator =
              new DefaultTrackingModifiedEntitiesRevisionInfoGenerator(
                  revisionInfoEntityName,
                  revisionInfoClass,
                  revisionListenerClass,
                  revisionInfoTimestampData,
                  isTimestampAsDate(),
                  modifiedEntityNamesData);
          globalCfg.setTrackEntitiesChangedInRevisionEnabled(true);
        } else {
          revisionInfoGenerator =
              new DefaultRevisionInfoGenerator(
                  revisionInfoEntityName,
                  revisionInfoClass,
                  revisionListenerClass,
                  revisionInfoTimestampData,
                  isTimestampAsDate());
        }
      }
    }

    // In case of a custom revision info generator, the mapping will be null.
    Document revisionInfoXmlMapping = null;

    Class<? extends RevisionListener> revisionListenerClass =
        getRevisionListenerClass(RevisionListener.class);

    if (revisionInfoGenerator == null) {
      if (globalCfg.isTrackEntitiesChangedInRevisionEnabled()) {
        revisionInfoClass = DefaultTrackingModifiedEntitiesRevisionEntity.class;
        revisionInfoEntityName = DefaultTrackingModifiedEntitiesRevisionEntity.class.getName();
        revisionInfoGenerator =
            new DefaultTrackingModifiedEntitiesRevisionInfoGenerator(
                revisionInfoEntityName,
                revisionInfoClass,
                revisionListenerClass,
                revisionInfoTimestampData,
                isTimestampAsDate(),
                modifiedEntityNamesData);
      } else {
        revisionInfoClass = DefaultRevisionEntity.class;
        revisionInfoGenerator =
            new DefaultRevisionInfoGenerator(
                revisionInfoEntityName,
                revisionInfoClass,
                revisionListenerClass,
                revisionInfoTimestampData,
                isTimestampAsDate());
      }
      revisionInfoXmlMapping = generateDefaultRevisionInfoXmlMapping();
    }

    return new RevisionInfoConfigurationResult(
        revisionInfoGenerator,
        revisionInfoXmlMapping,
        new RevisionInfoQueryCreator(
            revisionInfoEntityName,
            revisionInfoIdData.getName(),
            revisionInfoTimestampData.getName(),
            isTimestampAsDate()),
        generateRevisionInfoRelationMapping(),
        new RevisionInfoNumberReader(revisionInfoClass, revisionInfoIdData),
        globalCfg.isTrackEntitiesChangedInRevisionEnabled()
            ? new ModifiedEntityNamesReader(revisionInfoClass, modifiedEntityNamesData)
            : null,
        revisionInfoEntityName,
        revisionInfoClass,
        revisionInfoTimestampData);
  }
  private void searchForRevisionInfoCfgInProperties(
      XClass clazz,
      ReflectionManager reflectionManager,
      MutableBoolean revisionNumberFound,
      MutableBoolean revisionTimestampFound,
      MutableBoolean modifiedEntityNamesFound,
      String accessType) {
    for (XProperty property : clazz.getDeclaredProperties(accessType)) {
      RevisionNumber revisionNumber = property.getAnnotation(RevisionNumber.class);
      RevisionTimestamp revisionTimestamp = property.getAnnotation(RevisionTimestamp.class);
      ModifiedEntityNames modifiedEntityNames = property.getAnnotation(ModifiedEntityNames.class);

      if (revisionNumber != null) {
        if (revisionNumberFound.isSet()) {
          throw new MappingException("Only one property may be annotated with @RevisionNumber!");
        }

        XClass revisionNumberClass = property.getType();
        if (reflectionManager.equals(revisionNumberClass, Integer.class)
            || reflectionManager.equals(revisionNumberClass, Integer.TYPE)) {
          revisionInfoIdData =
              new PropertyData(property.getName(), property.getName(), accessType, null);
          revisionNumberFound.set();
        } else if (reflectionManager.equals(revisionNumberClass, Long.class)
            || reflectionManager.equals(revisionNumberClass, Long.TYPE)) {
          revisionInfoIdData =
              new PropertyData(property.getName(), property.getName(), accessType, null);
          revisionNumberFound.set();

          // The default is integer
          revisionPropType = "long";
        } else {
          throw new MappingException(
              "The field annotated with @RevisionNumber must be of type "
                  + "int, Integer, long or Long");
        }

        // Getting the @Column definition of the revision number property, to later use that info to
        // generate the same mapping for the relation from an audit table's revision number to the
        // revision entity revision number.
        Column revisionPropColumn = property.getAnnotation(Column.class);
        if (revisionPropColumn != null) {
          revisionPropSqlType = revisionPropColumn.columnDefinition();
        }
      }

      if (revisionTimestamp != null) {
        if (revisionTimestampFound.isSet()) {
          throw new MappingException("Only one property may be annotated with @RevisionTimestamp!");
        }

        XClass revisionTimestampClass = property.getType();
        if (reflectionManager.equals(revisionTimestampClass, Long.class)
            || reflectionManager.equals(revisionTimestampClass, Long.TYPE)
            || reflectionManager.equals(revisionTimestampClass, Date.class)
            || reflectionManager.equals(revisionTimestampClass, java.sql.Date.class)) {
          revisionInfoTimestampData =
              new PropertyData(property.getName(), property.getName(), accessType, null);
          revisionTimestampFound.set();
        } else {
          throw new MappingException(
              "The field annotated with @RevisionTimestamp must be of type "
                  + "long, Long, java.util.Date or java.sql.Date");
        }
      }

      if (modifiedEntityNames != null) {
        if (modifiedEntityNamesFound.isSet()) {
          throw new MappingException(
              "Only one property may be annotated with @ModifiedEntityNames!");
        }
        XClass modifiedEntityNamesClass = property.getType();
        if (reflectionManager.equals(modifiedEntityNamesClass, Set.class)
            && reflectionManager.equals(property.getElementClass(), String.class)) {
          modifiedEntityNamesData =
              new PropertyData(property.getName(), property.getName(), accessType, null);
          modifiedEntityNamesFound.set();
        } else {
          throw new MappingException(
              "The field annotated with @ModifiedEntityNames must be of Set<String> type.");
        }
      }
    }
  }