package org.intermine.dataloader;

/*
 * Copyright (C) 2002-2009 FlyMine
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  See the LICENSE file for more
 * information or http://www.gnu.org/copyleft/lesser.html.
 *
 */

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.intermine.metadata.Model;
import org.intermine.util.TypeUtil;

/**
 * Class holding data source priority configuration for a Model.
 *
 * @author Matthew Wakeling
 */
public class PriorityConfig
{
    private Map<String, List<String>> descriptors;
    private Map<ClassAndFieldName, List<String>> cache
        = new HashMap<ClassAndFieldName, List<String>>();

    /**
     * Constructor.
     *
     * @param model the Model of the production database
     */
    public PriorityConfig(Model model) {
        descriptors = DataLoaderHelper.getDescriptors(model);
    }

    /**
     * Returns a List of data source names representing the priority for a given class and
     * fieldname pair. This method searches all the parent classes for a priority config, and
     * throws an exception if more than one is found. Results are cached for performance.
     *
     * @param clazz the class of the object being created
     * @param fieldName the name of the field that the priority is for
     * @return a List of data source names
     * @throws IllegalArgumentException if more than one priority config matches
     */
    protected synchronized List<String> getPriorities(Class clazz, String fieldName)
    throws IllegalArgumentException {
        ClassAndFieldName cafn = new ClassAndFieldName(clazz, fieldName);
        List<String> retval = cache.get(cafn);
        if (retval == null) {
            String firstHit = null;
            Set<Class> done = new HashSet<Class>();
            Stack<Class> todo = new Stack<Class>();
            todo.push(clazz);
            while (!todo.empty()) {
                Class next = todo.pop();
                if (!done.contains(next)) {
                    String className = TypeUtil.unqualifiedName(next.getName());
                    if (next.getSuperclass() != null) {
                        todo.push(next.getSuperclass());
                    }
                    for (Class inter : next.getInterfaces()) {
                        todo.push(inter);
                    }
                    String thisHit = className + "." + fieldName;
                    List<String> possibleResult = descriptors.get(thisHit);
                    if (possibleResult == null) {
                        thisHit = className;
                        possibleResult = descriptors.get(thisHit);
                    }
                    if (possibleResult != null) {
                        if (retval == null) {
                            retval = possibleResult;
                            firstHit = thisHit;
                        } else {
                            throw new IllegalArgumentException("There are multiple priorities"
                                    + " configured for " + clazz.getName() + "." + fieldName
                                    + ". Found a match on " + firstHit + " and " + thisHit);
                        }
                    }
                    done.add(next);
                }
            }
            cache.put(cafn, retval);
        }
        return retval;
    }

    private static class ClassAndFieldName
    {
        private Class clazz;
        private String fieldName;

        public ClassAndFieldName(Class clazz, String fieldName) {
            this.clazz = clazz;
            this.fieldName = fieldName;
        }

        public int hashCode() {
            return 3 * clazz.hashCode() + 5 * fieldName.hashCode();
        }

        public boolean equals(Object o) {
            if (o instanceof ClassAndFieldName) {
                ClassAndFieldName c = (ClassAndFieldName) o;
                return clazz.equals(c.clazz) && fieldName.equals(c.fieldName);
            }
            return false;
        }

        public String toString() {
            return clazz.getName() + "." + fieldName;
        }
    }
}

@@ -470,16 +470,110 @@
 Stack;%0A%0A
+import org.intermine.metadata.ClassDescriptor;%0Aimport org.intermine.metadata.FieldDescriptor;%0A
 import o
@@ -1029,16 +1029,92 @@
 atabase%0A
+     * @throws IllegalArgumentException if the priorities are misconfigured%0A
      */%0A
@@ -1151,16 +1151,48 @@
 l model)
+ throws IllegalArgumentException
  %7B%0A     
@@ -1244,24 +1244,1115 @@
 ors(model);%0A
+        for (String key : descriptors.keySet()) %7B%0A            if (key.indexOf(%22.%22) == -1) %7B%0A                ClassDescriptor cld = model.getClassDescriptorByName(key);%0A                Class clazz = cld.getType();%0A                for (FieldDescriptor field : cld.getFieldDescriptors()) %7B%0A                    if (!field.isCollection()) %7B%0A                        getPriorities(clazz, field.getName());%0A                    %7D%0A                %7D%0A            %7D else %7B%0A                String className = key.substring(0, key.indexOf(%22.%22));%0A                String fieldName = key.substring(key.indexOf(%22.%22) + 1);%0A                ClassDescriptor cld = model.getClassDescriptorByName(className);%0A                Class clazz = cld.getType();%0A                FieldDescriptor field = cld.getFieldDescriptorByName(fieldName);%0A                if ((field != null) && (!field.isCollection())) %7B%0A                    getPriorities(clazz, fieldName);%0A                %7D else %7B%0A                    throw new IllegalArgumentException(%22Bad entry in priorities file: %22 + key);%0A                %7D%0A            %7D%0A        %7D%0A
     %7D%0A%0A    /

Verify priority config