content: package org.sql2o.reflection2;
  
  import org.sql2o.tools.AbstractCache;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Member;
  import java.lang.reflect.Method;
  import java.util.*;
  
  import static java.beans.Introspector.decapitalize;
  import static java.lang.reflect.Modifier.isPrivate;
  import static java.lang.reflect.Modifier.isStatic;
  
  /**
   * User: dimzon
   * Date: 4/9/14
   * Time: 1:10 AM
   */
  
  // TODO: move introspection code from PojoMetadata to PojoIntrospector
  
  @SuppressWarnings("UnusedDeclaration")
  public class PojoIntrospector {
      private static final AbstractCache<Class<?>, Map<String, ReadableProperty>, Void> rpCache =
              new AbstractCache<Class<?>, Map<String, ReadableProperty>, Void>() {
                  @Override
                  protected Map<String, ReadableProperty> evaluate(Class<?> key, Void param) {
                      return collectReadableProperties(key);
                  }
              };
  
      private static Map<String, ReadableProperty> collectReadableProperties(Class<?> cls) {
          Map<String, ReadableProperty> map = new HashMap<String, ReadableProperty>();
          List<Class<?>> classList = classInheritanceHierarhy(cls, Object.class);
          for (Class<?> aClass : classList) {
              collectPropertyGetters(map, aClass);
          }
          for (Class<?> aClass : classList) {
              collectReadableFields(map, aClass);
          }
          return Collections.unmodifiableMap(map);
      }
  
      public static Map<String, ReadableProperty> readableProperties(Class<?> ofClass) {
          return rpCache.get(ofClass, null);
      }
  
      private static void collectReadableFields(Map<String, ReadableProperty> map, Class<?> cls) {
          for (final Field m : cls.getDeclaredFields()) {
              if (isStaticOrPrivate(m)) continue;
              String propName = m.getName();
              if (map.containsKey(propName)) continue;
              Class<?> returnType = m.getType();
              m.setAccessible(true);
              ReadableProperty rp = new ReadableProperty(propName, returnType) {
                  @Override
                  public Object get(Object instance) throws InvocationTargetException, IllegalAccessException {
                      return m.get(instance);
                  }
              };
              map.put(propName, rp);
          }
      }
  
      private static boolean isStaticOrPrivate(Member m) {
          final int modifiers = m.getModifiers();
          return isStatic(modifiers) || isPrivate(modifiers);
      }
  
      private static void collectPropertyGetters(Map<String, ReadableProperty> map, Class<?> cls) {
          for (final Method m : cls.getDeclaredMethods()) {
              if (isStatic(m.getModifiers())) continue;
              if (isPrivate(m.getModifiers())) continue;
              if (0 != m.getParameterTypes().length) continue;
              Class<?> returnType = m.getReturnType();
              if (returnType == Void.TYPE || returnType == Void.class) continue;
              String name = m.getName();
              String propName = null;
              if (name.startsWith("get") && name.length() > 3) {
                  propName = decapitalize(name.substring(3));
              } else if (name.startsWith("is") && name.length() > 2 && returnType == Boolean.TYPE) {
                  propName = decapitalize(name.substring(2));
              }
              if (propName == null) continue;
              if (map.containsKey(propName)) continue;
              m.setAccessible(true);
              ReadableProperty rp = new ReadableProperty(propName, returnType) {
                  @Override
                  public Object get(Object instance) throws InvocationTargetException, IllegalAccessException {
                      return m.invoke(instance, (Object[]) null);
                  }
              };
              map.put(propName, rp);
          }
      }
  
      private static List<Class<?>> classInheritanceHierarhy(Class<?> cls, Class<Object> stopAt) {
          ArrayList<Class<?>> list = new ArrayList<Class<?>>();
          while (cls != null && cls != stopAt) {
              list.add(cls);
              cls = cls.getSuperclass();
          }
          return list;
      }
  
      public abstract static class ReadableProperty {
          public final String name;
          public final Class<?> type;
  
          private ReadableProperty(String name, Class<?> type) {
              this.name = name;
              this.type = type;
          }
  
          public abstract Object get(Object instance) throws InvocationTargetException, IllegalAccessException;
      }
  }
  