/*
 * Copyright 2007 the original author or authors.
 *
 * 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 grails.plugin.searchable.internal.util;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.codehaus.groovy.grails.commons.GrailsDomainClass;
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
import org.codehaus.groovy.grails.orm.hibernate.cfg.CompositeIdentity;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinder;
import org.codehaus.groovy.grails.orm.hibernate.cfg.Mapping;
import org.springframework.util.Assert;

/**
 * @author Maurice Nicholson
 */
public class GrailsDomainClassUtils {

    /**
     * Is the given property an identity property? Checks the property as well as the custom domain class mapping
     * @param domainClassProperty domain clas property
     * @return true if the property is the/an identity property
     */
    public static boolean isIndentityProperty(GrailsDomainClassProperty domainClassProperty) {
        Mapping mapping = GrailsDomainBinder.getMapping(domainClassProperty.getDomainClass().getClazz());
        if (mapping != null && mapping.getIdentity() instanceof CompositeIdentity) {
            CompositeIdentity identity = (CompositeIdentity) mapping.getIdentity();
            return Arrays.asList(identity.getPropertyNames()).contains(domainClassProperty.getName());
        }
        return domainClassProperty.isIdentity();
    }

    /**
     * Get the subclasses for the given GrailsDomainClass, filtering out any not in the given collection
     * @param grailsDomainClass the GrailsDomainClass to find subclasses for
     * @param grailsDomainClasses the collection of valid sub-classes
     * @return the collection of subclasses, if any
     */
    public static Collection getSubClasses(GrailsDomainClass grailsDomainClass, Collection grailsDomainClasses) {
        Set subClasses = new HashSet(grailsDomainClass.getSubClasses());
        subClasses.retainAll(grailsDomainClasses);
        return subClasses;
    }

    /**
     * Get the parent GrailsDomainClass for the given GrailsDomainClass, if it
     * exists in the given collection otherwise null
     *
     * @param grailsDomainClass the class whose parent to find
     * @param grailsDomainClasses the collection of possible parents
     * @return null if the given class has no parent or the parent is not in the collection
     */
    public static GrailsDomainClass getSuperClass(GrailsDomainClass grailsDomainClass, Collection grailsDomainClasses) {
        Set candidates = new HashSet();
        for (Iterator iter = grailsDomainClasses.iterator(); iter.hasNext(); ) {
            GrailsDomainClass gdc = (GrailsDomainClass) iter.next();
            if (gdc.getSubClasses().contains(grailsDomainClass)) {
                candidates.add(gdc);
            }
        }
        if (candidates.isEmpty()) {
            return null;
        }
        while (candidates.size() > 1) {
            Set copy = new HashSet(candidates);
            for (Iterator iter = copy.iterator(); iter.hasNext(); ) {
                GrailsDomainClass supsup = (GrailsDomainClass) iter.next();
                boolean remove = false;
                for (Iterator iter2 = candidates.iterator(); iter2.hasNext(); ) {
                    GrailsDomainClass sup = (GrailsDomainClass) iter2.next();
                    if (supsup.getSubClasses().contains(sup)) {
                        remove = true;
                        break;
                    }
                }
                if (remove) {
                    candidates.remove(supsup);
                    break;
                }
            }
        }
        return (GrailsDomainClass) candidates.iterator().next();
    }

    /**
     * Get all the super-classes in the given GrailsDomainClass's hierarchy
     * @param grailsDomainClass
     * @param grailsDomainClasses
     * @return
     */
    public static Collection getSuperClasses(GrailsDomainClass grailsDomainClass, Collection grailsDomainClasses) {
        Set superClasses = new HashSet();
        do {
            GrailsDomainClass superClass = getSuperClass(grailsDomainClass, grailsDomainClasses);
            if (superClass != null) {
                superClasses.add(superClass);
            }
            grailsDomainClass = superClass;
        } while (grailsDomainClass != null);
        return superClasses;
    }

    /**
     * Get the actual (user-defined) Classes for the given GrailsDomainClass Collection.
     * Equivalent to collecting the results of <code>grailsDomainClass.getClazz()</code> on
     * each element
     *
     * @param grailsDomainClasses
     * @return A collection of User-defined classes, which may be empty
     */
    public static Collection getClazzes(Collection grailsDomainClasses) {
        if (grailsDomainClasses == null || grailsDomainClasses.isEmpty()) {
            return Collections.EMPTY_SET;
        }
        Set clazzes = new HashSet();
        for (Iterator iter = grailsDomainClasses.iterator(); iter.hasNext(); ) {
            clazzes.add(((GrailsDomainClass) iter.next()).getClazz());
        }
        return clazzes;
    }

    /**
     * Gets the GrailsDomainClass for the given user-defined clazz
     * @param clazz the user-defined domain class
     * @param grailsDomainClasses the collection of GrailsDomainClasses to look thru
     * @return the corresponding GrailsDomainClass
     */
    public static GrailsDomainClass getGrailsDomainClass(Class clazz, Collection grailsDomainClasses) {
        for (Iterator iter = grailsDomainClasses.iterator(); iter.hasNext(); ) {
            GrailsDomainClass grailsDomainClass = (GrailsDomainClass) iter.next();
            if (grailsDomainClass.getClazz().equals(clazz)) {
                return grailsDomainClass;
            }
        }
        return null;
    }

    /**
     * Returns the named property for the given domain class's clazz
     * @param grailsDomainClasses a collection GrailsDomainClass
     * @param clazz the user domain Class
     * @param propertyName the property name
     * @return the property
     */
    public static GrailsDomainClassProperty getGrailsDomainClassProperty(Collection grailsDomainClasses, Class clazz, String propertyName) {
        GrailsDomainClass grailsDomainClass = getGrailsDomainClass(clazz, grailsDomainClasses);
        Assert.notNull(grailsDomainClass, "GrailsDomainClass not found for clazz [" + clazz + "]");
        return grailsDomainClass.getPropertyByName(propertyName);
    }

    /**
     * Is the given GrailsDomainClass with an inheritance hierarchy with the given collection?
     * @param grailsDomainClass the grails domain class
     * @param grailsDomainClasses the collection of grails domain classes
     * @return true if the given class is within a hierarchy: it has super or sub-classes
     */
    public static boolean isWithinInhertitanceHierarchy(GrailsDomainClass grailsDomainClass, Collection grailsDomainClasses) {
        if (getSuperClass(grailsDomainClass, grailsDomainClasses) != null) {
            return true;
        }
        for (Iterator iter = grailsDomainClass.getSubClasses().iterator(); iter.hasNext(); ) {
            Object o = iter.next();
            if (grailsDomainClasses.contains(o)) {
                return true;
            }
        }
        return false;
    }
}

@@ -1638,16 +1638,20 @@
 apping =
+ new
  GrailsD
@@ -1661,16 +1661,18 @@
 inBinder
+()
 .getMapp

fix for Grails 2.3 and hibernate 3.6.10+, GrailsDomainBinder no longer has static methods, so for the plugin to work with older and newer hibernate plugins create an instance of GrailsDomainBinder (it's still stateless)