/*
 * Copyright (C) 2011 Benoit GUEROUT <bguerout at gmail dot com> and Yves AMSELLEM <amsellem dot yves at gmail dot com>
 *
 * 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 org.jongo.marshall.jackson;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.bson.types.ObjectId;
import org.jongo.ObjectIdUpdater;
import org.jongo.marshall.jackson.id.Id;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class JacksonObjectIdUpdater implements ObjectIdUpdater {

    private final Map<Class<?>, Field> idFields = new HashMap<Class<?>, Field>();

    public boolean canSetObjectId(Object target) {
        Field field = findFieldOrNull(target.getClass());
        return field != null && getTargetValue(target, field) == null;
    }

    public void setDocumentGeneratedId(Object target, ObjectId id) {
        Field field = findFieldOrNull(target.getClass());
        if (field == null) {
            throw new IllegalArgumentException("Unable to set objectid on class: " + target.getClass());
        }
        updateField(target, id, field);
    }

    private void updateField(Object target, ObjectId id, Field field) {
        Object value = getTargetValue(target, field);
        if (value == null) {
            try {
                if (field.getType().equals(ObjectId.class)) {
                    field.set(target, id);
                } else if (field.getType().equals(String.class)) {
                    field.set(target, id.toString());
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Unable to set objectid on class: " + target.getClass(), e);
            }
        }
    }

    private Object getTargetValue(Object target, Field field) {
        try {
            if (field != null) {
                field.setAccessible(true);
                return field.get(target);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to obtain value from field" + field.getName() + ", class: " + target.getClass(), e);
        }
        return null;
    }

    protected Field findFieldOrNull(Class<?> clazz) {
        if (idFields.containsKey(clazz)) {
            return idFields.get(clazz);
        }

        while (!Object.class.equals(clazz)) {
            Field[] declaredFields = clazz.getDeclaredFields();
            if (declaredFields == null) {
                return null;
            }
            for (Field f : declaredFields) {
                if (isId(f.getName()) || isJacksonAnnotated(f) || isIdAnnotated(f)) {
                    idFields.put(clazz, f);
                    return f;
                }
            }
            clazz = clazz.getSuperclass();
        }

        return null;
    }

    private boolean isJacksonAnnotated(Field f) {
        JsonProperty annotation = f.getAnnotation(JsonProperty.class);
        return annotation != null && isId(annotation.value());
    }

    private boolean isIdAnnotated(Field f) {
        Id annotation = f.getAnnotation(Id.class);
        return annotation != null;
    }

    private boolean isId(String value) {
        return "_id".equals(value);
    }
}

@@ -1106,16 +1106,159 @@
 ield%3E();
+%0A    private final ObjectIdSelector fieldSelector;%0A%0A    public JacksonObjectIdUpdater() %7B%0A        fieldSelector = new ObjectIdSelector();%0A    %7D
 %0A%0A    pu
@@ -3194,66 +3194,46 @@
 if (
-isId(f.getName()) %7C%7C isJacksonAnnotated(f) %7C%7C isIdAnnotate
+fieldSelector.canBeConsideredAsObjectI
 d(f)
@@ -3416,32 +3416,235 @@
 rn null;%0A    %7D%0A%0A
+    private static class ObjectIdSelector %7B%0A%0A        public boolean canBeConsideredAsObjectId(Field f) %7B%0A            return isId(f.getName()) %7C%7C isJacksonAnnotated(f) %7C%7C isIdAnnotated(f);%0A        %7D%0A%0A    
     private bool
@@ -3685,16 +3685,20 @@
         
+    
 JsonProp
@@ -3744,32 +3744,36 @@
 roperty.class);%0A
+    
         return a
@@ -3819,31 +3819,39 @@
 lue());%0A    
-%7D%0A%0A
+    %7D%0A%0A   
     
+ 
 private bool
@@ -3871,32 +3871,36 @@
 ated(Field f) %7B%0A
+    
         Id annot
@@ -3934,32 +3934,36 @@
 class);%0A        
+   
+ 
 return annotatio
@@ -3969,39 +3969,47 @@
 on != null;%0A    
-%7D%0A%0A
+    %7D%0A%0A   
     
+ 
 private boolean 
@@ -4033,24 +4033,28 @@
 ) %7B%0A        
+    
 return %22_id%22
@@ -4065,20 +4065,30 @@
 als(value);%0A
+        %7D%0A
     %7D%0A%7D%0A

Move Jackson ObjectId detection into a dedicated class