/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.model.group.processor;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.InjectorBuilder.InjectorObjectFactory;
import ro.isdc.wro.util.ObjectDecorator;


/**
 * Injector scans some object fields and checks if a value can be provided to a field; Injector will ignore all non-null
 * fields.
 *
 * @author Alex Objelean
 * @created 20 Nov 2010
 */
public final class Injector {
  private static final Logger LOG = LoggerFactory.getLogger(Injector.class);
  private final Map<Class<?>, Object> map;
  private final Set<Object> injectedObjects = new HashSet<Object>();
  /**
   * Mapping of classes to be annotated and the corresponding injected object.
   */
  Injector(final Map<Class<?>, Object> map) {
    Validate.notNull(map);
    this.map = map;
  }

  /**
   * Scans the object and inject the supported values into the fields having @Inject annotation present.
   *
   * @param object
   *          {@link Object} which will be scanned for @Inject annotation presence.
   * @return the injected object instance. Useful for fluent interface.
   */
  public <T>T inject(final T object) {
    Validate.notNull(object);
    if (!injectedObjects.contains(object)) {
      processInjectAnnotation(object);
      injectedObjects.add(object);
    }
    return object;
  }

  /**
   * Check for each field from the passed object if @Inject annotation is present & inject the required field if
   * supported, otherwise warns about invalid usage.
   *
   * @param object
   *          to check for annotation presence.
   */
  private void processInjectAnnotation(final Object object) {
    LOG.info("inject: {}", object.getClass());
    try {
      final Collection<Field> fields = getAllFields(object);
      for (final Field field : fields) {
        if (field.isAnnotationPresent(Inject.class)) {
          if (!acceptAnnotatedField(object, field)) {
            final String message = String.format(
                "@Inject cannot be applied on object: %s to field of type: %s using injector %s", object,
                field.getType(), this);
            LOG.error(message + ". Supported types are: {}", map.keySet());
            throw new WroRuntimeException(message);
          }
        }
      }
      // handle special cases like decorators. Perform recursive injection
      if (object instanceof ObjectDecorator) {
        processInjectAnnotation(((ObjectDecorator<?>) object).getDecoratedObject());
      }
    } catch (final Exception e) {
      LOG.error("Error while scanning @Inject annotation", e);
      throw WroRuntimeException.wrap(e, "Exception while trying to process @Inject annotation on object: " + object);
    }
  }

  /**
   * Return all fields for given object, also those from the super classes.
   */
  private Collection<Field> getAllFields(final Object object) {
    final Collection<Field> fields = new ArrayList<Field>();
    fields.addAll(Arrays.asList(object.getClass().getDeclaredFields()));
    // inspect super classes
    Class<?> superClass = object.getClass().getSuperclass();
    while (superClass != null) {
      fields.addAll(Arrays.asList(superClass.getDeclaredFields()));
      superClass = superClass.getSuperclass();
    }
    return fields;
  }

  /**
   * Analyze the field containing {@link Inject} annotation and set its value to appropriate value. Override this method
   * if you want to inject something else but uriLocatorFactory.
   *
   * @param object
   *          an object containing @Inject annotation.
   * @param field
   *          {@link Field} object containing {@link Inject} annotation.
   * @return true if field was injected with some not null value.
   * @throws IllegalAccessException
   */
  private boolean acceptAnnotatedField(final Object object, final Field field)
      throws IllegalAccessException {
    boolean accept = false;
    // accept private modifiers
    field.setAccessible(true);
    if (!Context.isContextSet()) {
      throw new WroRuntimeException("No Context Set");
    }
    for (final Map.Entry<Class<?>, Object> entry : map.entrySet()) {
      if (entry.getKey().isAssignableFrom(field.getType())) {
        Object value = entry.getValue();
        // treat factories as a special case for lazy load of the objects.
        if (value instanceof InjectorObjectFactory) {
          value = ((InjectorObjectFactory<?>) value).create();
          inject(value);
        }
        field.set(object, value);
        accept = true;
        break;
      }
    }
    return accept;
  }
}

@@ -212,23 +212,19 @@
 va.util.
-HashSet
+Map
 ;%0D%0Aimpor
@@ -239,34 +239,19 @@
 til.
-Map;%0D%0Aimport java.util.Set
+WeakHashMap
 ;%0D%0A%0D
@@ -967,26 +967,35 @@
 e final 
-Set
+Map
 %3CObject
+, Boolean
 %3E inject
@@ -1014,28 +1014,43 @@
 new 
+Weak
 Hash
-Set
+Map
 %3CObject
+, Boolean
 %3E();%0D%0A
+%0D%0A
   /*
@@ -1558,16 +1558,17 @@
 blic %3CT%3E
+ 
 T inject
@@ -1651,16 +1651,19 @@
 contains
+Key
 (object)
@@ -1733,18 +1733,49 @@
 cts.
-add(object
+put(System.identityHashCode(object), true
 );%0D%0A
@@ -2132,56 +2132,8 @@
  %7B%0D%0A
-    LOG.info(%22inject: %7B%7D%22, object.getClass());%0D%0A
     

use identityHashCode instead of object instance & WeakHashMap to hold injected objects.