/*
 * Copyright 2014 Squarespace, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.squarespace.jersey2.guice;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.glassfish.hk2.api.ServiceLocatorFactory;
import org.glassfish.hk2.extension.ServiceLocatorGenerator;
import org.glassfish.hk2.internal.ServiceLocatorFactoryImpl;
import org.glassfish.jersey.internal.inject.Injections;
import org.jvnet.hk2.external.generator.ServiceLocatorGeneratorImpl;

import com.google.inject.Guice;

/**
 * This utility class provides some brute-force setter methods to make HK2 work 
 * with {@link Guice}. The underlying problem is that Jersey is written with 
 * extensibility in mind but totally fails to follow though with it. Some of the 
 * key classes are hard coded as {@code private static final} fields. There is 
 * an open JIRA for it.
 * 
 * @see https://java.net/jira/browse/JERSEY-2551
 */
class InjectionsUtils {

  private static final String GENERATOR_FIELD = "generator";
  
  private static final String FACTORY_FIELD = "factory";
  
  private static final String INSTANCE_FIELD = "INSTANCE";
  
  private static final String MODIFIERS_FIELD = "modifiers";
  
  /**
   * Installs the given {@link ServiceLocatorGenerator}.
   */
  public static void setServiceLocatorGenerator(ServiceLocatorGenerator generator) {
    install(Injections.class, GENERATOR_FIELD, generator);
  }
  
  /**
   * Installs the given {@link ServiceLocatorFactory}.
   */
  public static void setServiceLocatorFactory(ServiceLocatorFactory factory) {
    install(ServiceLocatorFactory.class, INSTANCE_FIELD, factory);
    install(Injections.class, FACTORY_FIELD, factory);
  }
  
  /**
   * Returns {@code true} if there is a non {@link ServiceLocatorGeneratorImpl} installed.
   */
  public static boolean isGeneratorInstalled() {
    return !equals(Injections.class, GENERATOR_FIELD, ServiceLocatorGeneratorImpl.class);
  }
  
  /**
   * Returns {@code true} if there is a non {@link ServiceLocatorFactoryImpl} installed.
   */
  public static boolean isFactoryInstalled() {
    return !equals(ServiceLocatorFactory.class, INSTANCE_FIELD, ServiceLocatorFactoryImpl.class)
        && !equals(Injections.class, FACTORY_FIELD, ServiceLocatorFactoryImpl.class);
  }
  
  private static void install(Class<?> clazz, String name, Object value) {
    try {
      Field field = clazz.getDeclaredField(name);
      field.setAccessible(true);
      
      // Turn off the 'final' if necessary!
      int modifiers = field.getModifiers();
      if (Modifier.isFinal(modifiers)) {
        setModifiers(field, modifiers & ~Modifier.FINAL);
        try {
          field.set(null, value);
        } finally {
          setModifiers(field, modifiers | Modifier.FINAL);
        }
      } else {
        field.set(null, value);
      }
      
    } catch (NoSuchFieldException | IllegalAccessException err) {
      throw new IllegalStateException(err);
    }
  }
  
  private static void setModifiers(Field dst, int modifiers) throws IllegalAccessException, NoSuchFieldException, SecurityException {
    Field field = Field.class.getDeclaredField(MODIFIERS_FIELD);
    field.setAccessible(true);
    field.setInt(dst, modifiers);
  }
  
  private static boolean equals(Class<?> clazz, String name, Class<?> type) {
    Object value = getValue(clazz, name);
    if (value != null && value.getClass().equals(type)) {
      return true;
    }
    
    return false;
  }
  
  private static Object getValue(Class<?> clazz, String name) {
    try {
      Field field = clazz.getDeclaredField(name);
      field.setAccessible(true);
      return field.get(null);
    } catch (NoSuchFieldException | IllegalAccessException err) {
      throw new IllegalStateException(err);
    }
  }
  
  private InjectionsUtils() {}
}

@@ -1000,16 +1000,73 @@
 torImpl;
+%0Aimport org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;
 %0A%0Aimport
@@ -1523,16 +1523,103 @@
 tils %7B%0A%0A
+  private static final Logger LOG = LoggerFactory.getLogger(InjectionsUtils.class);%0A  %0A
   privat
@@ -2010,24 +2010,36 @@
 enerator) %7B%0A
+    try %7B%0A  
     install(
@@ -2085,16 +2085,160 @@
 rator);%0A
+    %7D catch (NoSuchFieldException err) %7B%0A      LOG.trace(%22NoSuchFieldException: JERSEY-2551. This OK if you're using Jersey 2.11+%22, err);%0A    %7D%0A
   %7D%0A  %0A 
@@ -2378,24 +2378,36 @@
  factory) %7B%0A
+    try %7B%0A  
     install(
@@ -2461,24 +2461,26 @@
 ctory);%0A    
+  
 install(Inje
@@ -2518,16 +2518,107 @@
 ctory);%0A
+    %7D catch (NoSuchFieldException err) %7B%0A      throw new IllegalStateException(err);%0A    %7D%0A
   %7D%0A  %0A 
@@ -3275,16 +3275,44 @@
 t value)
+ throws NoSuchFieldException
  %7B%0A    t
@@ -3805,39 +3805,16 @@
  catch (
-NoSuchFieldException %7C 
 IllegalA

Make reflection work with Jersey 2.11