All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.hk2.xml.internal.Utilities Maven / Gradle / Ivy

There is a newer version: 4.0.0-M3
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2014-2017 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.hk2.xml.internal;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.xml.bind.annotation.XmlRootElement;

import org.glassfish.hk2.api.ActiveDescriptor;
import org.glassfish.hk2.api.DynamicConfiguration;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.configuration.hub.api.Instance;
import org.glassfish.hk2.configuration.hub.api.WriteableBeanDatabase;
import org.glassfish.hk2.configuration.hub.api.WriteableType;
import org.glassfish.hk2.utilities.AbstractActiveDescriptor;
import org.glassfish.hk2.utilities.BuilderHelper;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.hk2.utilities.general.GeneralUtilities;
import org.glassfish.hk2.utilities.reflection.ClassReflectionHelper;
import org.glassfish.hk2.utilities.reflection.ReflectionHelper;
import org.glassfish.hk2.xml.internal.Differences.AddData;
import org.glassfish.hk2.xml.internal.Differences.AddRemoveMoveDifference;
import org.glassfish.hk2.xml.internal.Differences.Difference;
import org.glassfish.hk2.xml.internal.Differences.MoveData;
import org.glassfish.hk2.xml.internal.Differences.RemoveData;
import org.glassfish.hk2.xml.internal.alt.AltClass;
import org.glassfish.hk2.xml.internal.alt.clazz.ClassAltClassImpl;
import org.glassfish.hk2.xml.internal.alt.papi.ArrayTypeAltClassImpl;
import org.glassfish.hk2.xml.internal.alt.papi.TypeElementAltClassImpl;
import org.glassfish.hk2.xml.jaxb.internal.BaseHK2JAXBBean;

/**
 * @author jwells
 *
 */
public class Utilities {
    /** Separator for instance names */
    public final static char INSTANCE_PATH_SEPARATOR = '.';
    
    private final static String CLASS_ADD_ON_NAME = "_Hk2_Jaxb";
    
    private final static String NOT_UNIQUE_UNIQUE_ID = "not-unique";
    
    private final static String EMPTY_STRING = "";
    
    /* package */ static String convertXmlRootElementName(XmlRootElement root, Class clazz) {
        if (!"##default".equals(root.name())) return root.name();
        
        String simpleName = clazz.getSimpleName();
        
        char asChars[] = simpleName.toCharArray();
        StringBuffer sb = new StringBuffer();
        
        boolean firstChar = true;
        boolean lastCharWasCapital = false;
        for (char asChar : asChars) {
            if (firstChar) {
                firstChar = false;
                if (Character.isUpperCase(asChar)) {
                    lastCharWasCapital = true;
                    sb.append(Character.toLowerCase(asChar));
                }
                else {
                    lastCharWasCapital = false;
                    sb.append(asChar);
                }
            }
            else {
                if (Character.isUpperCase(asChar)) {
                    if (!lastCharWasCapital) {
                        sb.append('-');
                    }
                    
                    sb.append(Character.toLowerCase(asChar));
                    
                    lastCharWasCapital = true;
                }
                else {
                    sb.append(asChar);
                    
                    lastCharWasCapital = false;
                }
            }
        }
        
        return sb.toString();
    }
    
    public static BaseHK2JAXBBean createBean(Class implClass) {
        try {
            Constructor noArgsConstructor = implClass.getConstructor();
    
            return (BaseHK2JAXBBean) ReflectionHelper.makeMe(noArgsConstructor, new Object[0], false);
        }
        catch (RuntimeException re) {
            throw re;
        }
        catch (Throwable th) {
            throw new RuntimeException(th);
        }
    }
    
    private static String getKeySegment(BaseHK2JAXBBean bean) {
        String baseKeySegment = bean._getKeyValue();
        if (baseKeySegment == null) {
            baseKeySegment = bean._getSelfXmlTag();
        }
        
        return baseKeySegment;
    }
    
    /**
     * Creates an instance name by traveling up the parent chain.  The
     * parent chain must therefor already be correctly setup
     * 
     * @param bean The non-null bean from where to get the instancename
     * @return A unique instance name.  The combination of the xml path
     * and the instance name should uniquely identify the location of
     * any node in a single tree
     */
    public static String createInstanceName(BaseHK2JAXBBean bean) {
        if (bean._getParent() == null) {
            return getKeySegment(bean);
        }
        
        return createInstanceName((BaseHK2JAXBBean) bean._getParent()) + INSTANCE_PATH_SEPARATOR + getKeySegment(bean);
    }
    
    public static ActiveDescriptor advertise(WriteableBeanDatabase wbd, DynamicConfiguration config, BaseHK2JAXBBean bean) {
        ActiveDescriptor selfDescriptor = null;
        if (config != null) {
            AbstractActiveDescriptor cDesc = BuilderHelper.createConstantDescriptor(bean);
            
            // We make these singletons to ensure that any InstanceLifecycleListeners will only be called once
            cDesc.setScopeAsAnnotation(ServiceLocatorUtilities.getSingletonAnnotation());
            
            if (bean._getKeyValue() != null) {
                cDesc.setName(bean._getKeyValue());
            }
            selfDescriptor = config.addActiveDescriptor(cDesc);
            
            bean._setSelfDescriptor(selfDescriptor);
        }
        
        if (wbd != null) {
            WriteableType wt = wbd.findOrAddWriteableType(bean._getXmlPath());
            wt.addInstance(bean._getInstanceName(), bean._getBeanLikeMap(), bean);
        }
        
        return selfDescriptor;
    }
    
    /**
     * Converts a getter name to a setter name (works with
     * both IS getters and GET getters)
     * 
     * @param getterName Non-null getter name starting with is or get
     * @return The corresponding setter name
     */
    public static String convertToSetter(String getterName) {
        if (getterName.startsWith(JAUtilities.IS)) {
            return JAUtilities.SET + getterName.substring(JAUtilities.IS.length());
        }
        
        if (!getterName.startsWith(JAUtilities.GET)) {
            throw new IllegalArgumentException("Unknown getter format: " + getterName);
        }
        
        return JAUtilities.SET + getterName.substring(JAUtilities.GET.length());
    }
    
    /**
     * Converts the Name from the Element to a String
     * @param name
     * @return
     */
    public static String convertNameToString(Name name) {
        if (name == null) return null;
        return name.toString();
    }
    
    public static AltClass convertTypeMirror(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
        if (TypeKind.VOID.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.VOID;
        }
        if (TypeKind.BOOLEAN.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.BOOLEAN;
        }
        if (TypeKind.INT.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.INT;
        }
        if (TypeKind.LONG.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.LONG;
        }
        if (TypeKind.BYTE.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.BYTE;
        }
        if (TypeKind.CHAR.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.CHAR;
        }
        if (TypeKind.SHORT.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.SHORT;
        }
        if (TypeKind.FLOAT.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.FLOAT;
        }
        if (TypeKind.DOUBLE.equals(typeMirror.getKind())) {
            return ClassAltClassImpl.DOUBLE;
        }
        if (TypeKind.DECLARED.equals(typeMirror.getKind())) {
            DeclaredType dt = (DeclaredType) typeMirror;
            
            TypeElement typeElement = (TypeElement) dt.asElement();
            
            TypeElementAltClassImpl addMe = new TypeElementAltClassImpl(typeElement, processingEnv);
            
            return addMe;
        }
        if (TypeKind.ARRAY.equals(typeMirror.getKind())) {
            ArrayType at = (ArrayType) typeMirror;
            
            return new ArrayTypeAltClassImpl(at, processingEnv);
        }
        if (TypeKind.TYPEVAR.equals(typeMirror.getKind())) {
            TypeVariable tv = (TypeVariable) typeMirror;
            
            TypeMirror upperBound = tv.getUpperBound();
            if (upperBound != null && TypeKind.DECLARED.equals(upperBound.getKind())) {
                return convertTypeMirror(upperBound, processingEnv);
            }
            
            return ClassAltClassImpl.OBJECT;
        }
        
        throw new AssertionError("Unknown parameter kind: " + typeMirror.getKind());
    }
    
    @SuppressWarnings("unchecked")
    public static BaseHK2JAXBBean internalAdd(
            BaseHK2JAXBBean myParent,
            String childProperty,
            Object rawChild,
            String childKey,
            int index,
            DynamicChangeInfo changeInformation,
            XmlDynamicChange xmlDynamicChange,
            List> addedServices,
            boolean changeList) {
        if (index < -1) {
            throw new IllegalArgumentException("Unknown index " + index);
        }
        
        if (childKey != null && myParent._lookupChild(childProperty, childKey) != null) {
            throw new IllegalStateException("There is already a child with name " + childKey + " for child " + childProperty);
        }
        
        if (rawChild != null && !(rawChild instanceof BaseHK2JAXBBean)) {
            throw new IllegalArgumentException("The child added must be from XmlService.createBean");
        }
        
        ParentedModel childNode = myParent._getModel().getChild(childProperty);
        if (childNode == null) {
            throw new IllegalArgumentException("There is no child with xmlTag " + childProperty + " of " + myParent);
        }
        
        Object allMyChildren = myParent._getProperty(childProperty);
        List multiChildren = null;
        if (!ChildType.DIRECT.equals(childNode.getChildType())) {
            if (allMyChildren == null) {
                multiChildren = new ArrayList(10);
            }
            else {
                if (ChildType.LIST.equals(childNode.getChildType())) {
                    multiChildren = new ArrayList((List) allMyChildren);
                }
                else {
                    multiChildren = new ArrayList(Arrays.asList((Object[]) allMyChildren));
                }
            }
            
            if (index > multiChildren.size()) {
                throw new IllegalArgumentException("The index given to add child " + childProperty + " to " + myParent + " is not in range");
            }
            
            if (index == -1) {
                index = multiChildren.size();
            }
        }
        else if (allMyChildren != null) {
            throw new IllegalStateException("Attempting to add direct child of " + myParent + " of name " + childProperty + " but there is already one there");
        }
        
        BaseHK2JAXBBean child = createBean(childNode.getChildModel().getProxyAsClass());
        child._setClassReflectionHelper(myParent._getClassReflectionHelper());
        
        if (rawChild != null) {
            // Handling of children will be handled once the real child is better setup
            BaseHK2JAXBBean childToCopy = (BaseHK2JAXBBean) rawChild;
            for (String nonChildProperty : childToCopy._getModel().getNonChildProperties().keySet()) {
                Object value = childToCopy._getProperty(nonChildProperty);
                if (value == null) continue;
                
                child._setProperty(nonChildProperty, value, false, true);
            }
        }
        
        if (childKey == null) {
            if (!ChildType.DIRECT.equals(childNode.getChildType())) {
                if (childNode.getChildModel().getKeyProperty() != null) {
                    if (rawChild != null) {
                        childKey = (String) child._getProperty(childNode.getChildModel().getKeyProperty());
                    }
                    
                    if (childKey == null) {
                        throw new IllegalArgumentException("Attempted to create child with xmlTag " + childProperty +
                            " with no key field in " + myParent);
                    }
                    
                    child._setKeyValue(childKey);
                }
                else {
                    // This is a multi-child with no key and no key property, must generate a key
                    if (myParent._getChangeControl() == null) {
                        childKey = NOT_UNIQUE_UNIQUE_ID;
                        child._setKeyValue(NOT_UNIQUE_UNIQUE_ID);
                    }
                    else {
                        childKey = myParent._getChangeControl().getGeneratedId();
                        child._setKeyValue(childKey);
                    }
                }
            }
        }
        else { /* childKey != null */
            if (childNode.getChildModel().getKeyProperty() == null) {
                throw new IllegalArgumentException("Attempted to add an unkeyed child with key " + childKey + " in " + myParent);
            }
                
            child._setProperty(childNode.getChildModel().getKeyProperty(), childKey, false, true);
            child._setKeyValue(childKey);
        }
        
        child._setParent(myParent);
        child._setSelfXmlTag(childNode.getChildXmlTag());
        child._setKeyValue(childKey);
        if (!ChildType.DIRECT.equals(childNode.getChildType())) {
            child._setInstanceName(myParent._getInstanceName() + Utilities.INSTANCE_PATH_SEPARATOR + child._getKeyValue());
        }
        else {
            child._setInstanceName(myParent._getInstanceName() + Utilities.INSTANCE_PATH_SEPARATOR + childNode.getChildXmlTag());
        }
        
        // Now freeze it
        child._setDynamicChangeInfo((XmlRootHandleImpl) myParent._getRoot(), changeInformation);
        
        externalAdd(child, xmlDynamicChange.getDynamicConfiguration(), xmlDynamicChange.getBeanDatabase(), addedServices);
        
        Utilities.invokeVetoableChangeListeners(changeInformation, child, null, child, EMPTY_STRING,
                myParent._getClassReflectionHelper());
        
        if (rawChild != null) {
            // Now we handle the children
            handleChildren(child, (BaseHK2JAXBBean) rawChild, changeInformation, addedServices, xmlDynamicChange);
        }
        
        // Now modify the actual list
        if (changeList && (multiChildren != null)) {
            // List or Array child
            multiChildren.add(index, child);
            
            Object finalChildList;
            if (ChildType.LIST.equals(childNode.getChildType())) {
                finalChildList = Collections.unmodifiableList(multiChildren);
            }
            else {
                // ARRAY
                finalChildList = Array.newInstance(childNode.getChildModel().getOriginalInterfaceAsClass(), multiChildren.size());
                for (int lcv = 0; lcv < multiChildren.size(); lcv++) {
                    Array.set(finalChildList, lcv, multiChildren.get(lcv));
                }
            }
            
            if (xmlDynamicChange.getBeanDatabase() != null) {
                myParent.changeInHub(childProperty, finalChildList, xmlDynamicChange.getBeanDatabase());
            }
            
            myParent._setProperty(childProperty, finalChildList, false, true);
        }
        else if (multiChildren == null) {
            // Direct child
            if (xmlDynamicChange.getBeanDatabase() != null){
                myParent.changeInHub(childProperty, child, xmlDynamicChange.getBeanDatabase());
            }
            
            myParent._setProperty(childProperty, child, false, true);
        }
        
        return child;
    }
    
    /**
     * Given the fully qualified class name of the interface representing a bean,
     * returns the class name of the proxy
     * 
     * @param iFaceName the never null fully qualified class name of the bean interface
     * @return the class name of the proxy
     */
    public static String getProxyNameFromInterfaceName(String iFaceName) {
        return iFaceName + CLASS_ADD_ON_NAME;
    }
    
    @SuppressWarnings("unchecked")
    private static void handleChildren(BaseHK2JAXBBean child,
            BaseHK2JAXBBean childToCopy,
            DynamicChangeInfo changeInformation,
            List> addedServices,
            XmlDynamicChange xmlDynamicChange) {
        Map childrenMap = childToCopy._getModel().getChildrenProperties();
        
        for (Map.Entry childsChildrenEntry : childrenMap.entrySet()) {
            String childsChildProperty = childsChildrenEntry.getKey();
            ParentedModel childsChildParentNode = childsChildrenEntry.getValue();
            
            if (!ChildType.DIRECT.equals(childsChildParentNode.getChildType())) {
                List childsChildren = null;
                if (ChildType.LIST.equals(childsChildParentNode.getChildType())) {
                    childsChildren = (List) childToCopy._getProperty(childsChildProperty);
                }
                else {
                    Object arrayChildsChildren = childToCopy._getProperty(childsChildProperty);
                    if (arrayChildsChildren != null) {
                        // This object is an array
                        childsChildren = new ArrayList(Array.getLength(arrayChildsChildren));
                        for (int lcv = 0; lcv < Array.getLength(arrayChildsChildren); lcv++) {
                            childsChildren.add(lcv, (BaseHK2JAXBBean) Array.get(arrayChildsChildren, lcv));
                        }
                    }
                }
                
                if (childsChildren == null) continue;
                if (childsChildren.size() <= 0) continue;
                
                ArrayList copiedChildArray = new ArrayList(childsChildren.size());
                Object asArray = Array.newInstance(childsChildParentNode.getChildModel().getOriginalInterfaceAsClass(), childsChildren.size());
                int lcv = 0;
                for (BaseHK2JAXBBean childsChild : childsChildren) {
                    BaseHK2JAXBBean grandchild = internalAdd(child, childsChildProperty,
                            childsChild, null, -1, changeInformation, xmlDynamicChange, addedServices, false);
                    
                    copiedChildArray.add(grandchild);
                    Array.set(asArray, lcv++, grandchild);
                }
                
                if (ChildType.LIST.equals(childsChildParentNode.getChildType())) {
                    child._setProperty(childsChildProperty, copiedChildArray, false, true);
                }
                else {
                    child._setProperty(childsChildProperty, asArray, false, true);
                }
            }
            else {
                BaseHK2JAXBBean childsChild = (BaseHK2JAXBBean) childToCopy._getProperty(childsChildProperty);
                if (childsChild == null) continue;
                
                BaseHK2JAXBBean grandchild = internalAdd(child, childsChildProperty,
                        childsChild, null, -1, changeInformation, xmlDynamicChange, addedServices, true);
                
                child._setProperty(childsChildProperty, grandchild, false, true);
            }
        }
    }
    
    @SuppressWarnings("unchecked")
    private static void externalAdd(BaseHK2JAXBBean root, DynamicConfiguration config, WriteableBeanDatabase writeableDatabase, List> addedDescriptors) {
        if (config == null && writeableDatabase == null) return;
        
        ActiveDescriptor added = Utilities.advertise(writeableDatabase, config, root);
        if (added != null) {
            addedDescriptors.add(added);
        }
        
        for (String keyedChildProperty : root._getModel().getKeyedChildren()) {
            Object keyedRawChild = root._getProperty(keyedChildProperty);
            if (keyedRawChild == null) continue;
            
            if (keyedRawChild instanceof Iterable) {
                Iterable iterable = (Iterable) keyedRawChild;
                for (BaseHK2JAXBBean child : iterable) {
                    externalAdd(child, config, writeableDatabase, addedDescriptors);
                }
            }
            else if (keyedRawChild.getClass().isArray()) {
                int aLength = Array.getLength(keyedRawChild);
                for (int lcv = 0; lcv < aLength; lcv++) {
                    BaseHK2JAXBBean child = (BaseHK2JAXBBean) Array.get(keyedRawChild, lcv);
                    externalAdd(child, config, writeableDatabase, addedDescriptors);
                }
                
            }
            else {
                externalAdd((BaseHK2JAXBBean) keyedRawChild, config, writeableDatabase, addedDescriptors);
            }
        }
        
        for (String unkeyedChildProperty : root._getModel().getUnKeyedChildren()) {
            Object unkeyedRawChild = root._getProperty(unkeyedChildProperty);
            if (unkeyedRawChild == null) continue;
            
            if (unkeyedRawChild instanceof Iterable) {
                Iterable unkeyedMultiChildren = (Iterable) unkeyedRawChild;
                for (BaseHK2JAXBBean child : unkeyedMultiChildren) {
                    externalAdd(child, config, writeableDatabase, addedDescriptors);
                }
            }
            else if (unkeyedRawChild.getClass().isArray()) {
                int aLength = Array.getLength(unkeyedRawChild);
                for (int lcv = 0; lcv < aLength; lcv++) {
                    BaseHK2JAXBBean child = (BaseHK2JAXBBean) Array.get(unkeyedRawChild, lcv);
                    externalAdd(child, config, writeableDatabase, addedDescriptors);
                }
            }
            else {
                externalAdd((BaseHK2JAXBBean) unkeyedRawChild, config, writeableDatabase, addedDescriptors);
            }
            
        }
    }
    
    /**
     * Called with write lock held
     * @param rootNode
     * @param rawRoot
     * @param changeInfo
     * @param helper
     * @param writeableDatabase
     * @param dynamicService
     * @return
     */
    public static BaseHK2JAXBBean _addRoot(ModelImpl rootNode,
            Object rawRoot,
            DynamicChangeInfo changeInfo,
            ClassReflectionHelper helper,
            WriteableBeanDatabase writeableDatabase,
            DynamicConfiguration dynamicService,
            List> addedServices,
            XmlRootHandleImpl xmlRootHandle) {
        if (!(rawRoot instanceof BaseHK2JAXBBean)) {
            throw new IllegalArgumentException("The root added must be from XmlService.createBean");
        }
        
        BaseHK2JAXBBean child = Utilities.createBean(rootNode.getProxyAsClass());
        child._setClassReflectionHelper(helper);
        
        // Handling of children will be handled once the real child is better setup
        BaseHK2JAXBBean childToCopy = (BaseHK2JAXBBean) rawRoot;
        for (String nonChildProperty : childToCopy._getModel().getNonChildProperties().keySet()) {
            Object value = childToCopy._getProperty(nonChildProperty);
            if (value == null) continue;
            
            child._setProperty(nonChildProperty, value, false);
        }
        
        if (rootNode.getKeyProperty() != null) {
            child._setKeyValue((String) child._getProperty(rootNode.getKeyProperty())); 
        }
        
        child._setSelfXmlTag(rootNode.getRootName());
        child._setInstanceName(rootNode.getRootName());
        
        handleChildren(child, childToCopy, changeInfo, addedServices, XmlDynamicChange.EMPTY);
            
        // Now freeze it
        child._setDynamicChangeInfo(xmlRootHandle, changeInfo);
        
        externalAdd(child, dynamicService, writeableDatabase, addedServices);
        
        return child;
    }
    
    /**
     * Write lock must be held
     * @param myParent
     * @param childProperty
     * @param childKey
     * @param index
     * @param changeInformation
     * @param writeableDatabase
     * @param dynamicService
     * @return
     */
    @SuppressWarnings("unchecked")
    public static BaseHK2JAXBBean internalRemove(
            BaseHK2JAXBBean myParent,
            String childProperty,
            String childKey,
            int index,
            Object childToRemove,
            DynamicChangeInfo changeInformation,
            XmlDynamicChange xmlDynamicChange,
            boolean changeList) {
        if (childProperty == null) return null;
        
        String instanceToRemove = null;
        if (childKey == null && index < 0 && childToRemove != null) {
            // Need to check that the parent is the same as me
            if (!(childToRemove instanceof BaseHK2JAXBBean)) {
                throw new IllegalArgumentException("Removed child must be a child of the parent " + myParent + " but is not of the correct type " +
                  childToRemove.getClass().getName());
            }
            
            BaseHK2JAXBBean childToRemoveBean = (BaseHK2JAXBBean) childToRemove;
            BaseHK2JAXBBean childToRemoveParent = (BaseHK2JAXBBean) childToRemoveBean._getParent();
            
            if (childToRemoveParent == null) {
                throw new IllegalArgumentException("Removed child must be a child of the parent " + myParent + " but has no parent of its own");
            }
            
            if (!childToRemoveParent.equals(myParent)) {
                throw new IllegalArgumentException("Removed child must be a child of the parent " + myParent + " but has a different parent " +
                  childToRemoveParent);
            }
            
            instanceToRemove = childToRemoveBean._getInstanceName();
        }
        
        ParentedModel removeMeParentedNode = myParent._getModel().getChild(childProperty);
        ModelImpl removeMeNode = removeMeParentedNode.getChildModel();
        BaseHK2JAXBBean rootForDeletion = null;
        
        if (!ChildType.DIRECT.equals(removeMeParentedNode.getChildType())) {
            if (childKey == null && index < 0 && instanceToRemove == null) return null;
            
            if (ChildType.LIST.equals(removeMeParentedNode.getChildType())) {
                List removeFromList = (List) myParent._getProperty(childProperty);
                
                if (removeFromList == null) return null;
                
                int minusOneSize = removeFromList.size() - 1;
                if (minusOneSize < 0) return null;
                
                List listWithObjectRemoved = new ArrayList(minusOneSize + 1);
                
                if (childKey != null || instanceToRemove != null) {
                    String comparisonKey = (childKey != null) ? childKey : instanceToRemove ;
                    
                    for (BaseHK2JAXBBean candidate : removeFromList) {
                        String candidateKeyValue;
                        if (childKey != null) {
                            candidateKeyValue = candidate._getKeyValue();
                        }
                        else {
                            candidateKeyValue = candidate._getInstanceName();
                        }
                        
                        if (GeneralUtilities.safeEquals(candidateKeyValue, comparisonKey)) {
                            rootForDeletion = candidate;
                        }
                        else {
                            listWithObjectRemoved.add(candidate);
                        }
                    }
                }
                else {
                    // unkeyed, index >= 0
                    if (index >= removeFromList.size()) {
                        return null;
                    }
                    
                    for (int lcv = 0; lcv < removeFromList.size(); lcv++) {
                        if (lcv == index) {
                            rootForDeletion = removeFromList.get(lcv);
                        }
                        else {
                            listWithObjectRemoved.add(removeFromList.get(lcv));
                        }
                    }
                }
                
                if (rootForDeletion == null) return null;
                
                if (changeList) {
                    if (xmlDynamicChange.getBeanDatabase() != null) {
                        myParent.changeInHub(childProperty, listWithObjectRemoved, xmlDynamicChange.getBeanDatabase());
                    }
                
                    myParent._setProperty(childProperty, listWithObjectRemoved, false, true);
                }
            }
            else {
                // array children
                Object removeFromArray = myParent._getProperty(childProperty);
                
                if (removeFromArray == null) return null;
                
                int removeFromArrayLength = Array.getLength(removeFromArray);
                if (removeFromArrayLength == 0) return null;
                
                Class arrayType = removeMeNode.getOriginalInterfaceAsClass();
                
                Object arrayWithObjectRemoved = Array.newInstance(arrayType, removeFromArrayLength - 1);
                
                if (childKey != null || instanceToRemove != null) {
                    String comparisonKey = (childKey != null) ? childKey : instanceToRemove ;
                    
                    int removeIndex = -1;
                    for (int lcv = 0; lcv < removeFromArrayLength; lcv++) {
                        BaseHK2JAXBBean candidate = (BaseHK2JAXBBean) Array.get(removeFromArray, lcv);
                        
                        String candidateKeyValue;
                        if (childKey != null) {
                            candidateKeyValue = candidate._getKeyValue();
                        }
                        else {
                            candidateKeyValue = candidate._getInstanceName();
                        }
                        
                        if (GeneralUtilities.safeEquals(candidateKeyValue, comparisonKey)) {
                            rootForDeletion = candidate;
                            removeIndex = lcv;
                            break;
                        }
                    }
                    
                    if (rootForDeletion == null) return null;
                    
                    int addIndex = 0;
                    for (int lcv = 0; lcv < removeFromArrayLength; lcv++) {
                        if (lcv == removeIndex) continue;
                        
                        Array.set(arrayWithObjectRemoved, addIndex++, Array.get(removeFromArray, lcv));
                    }
                }
                else if (index >= 0) {
                    // unkeyed, index >= 0
                    if (index >= removeFromArrayLength) {
                        return null;
                    }
                    
                    rootForDeletion = (BaseHK2JAXBBean) Array.get(removeFromArray, index);
                    
                    int addIndex = 0;
                    for (int lcv = 0; lcv < removeFromArrayLength; lcv++) {
                        if (lcv == index) continue;
                        
                        Array.set(arrayWithObjectRemoved, addIndex++, Array.get(removeFromArray, lcv));
                    }
                }
                else {
                    int removeIndex = -1;
                    for (int lcv = 0; lcv < removeFromArrayLength; lcv++) {
                        BaseHK2JAXBBean candidate = (BaseHK2JAXBBean) Array.get(removeFromArray, lcv);
                        
                        String candidateKeyValue = candidate._getInstanceName();
                        
                        if (GeneralUtilities.safeEquals(candidateKeyValue, instanceToRemove)) {
                            rootForDeletion = candidate;
                            removeIndex = lcv;
                            break;
                        }
                    }
                    
                    if (rootForDeletion == null) return null;
                    
                    int addIndex = 0;
                    for (int lcv = 0; lcv < removeFromArrayLength; lcv++) {
                        if (lcv == removeIndex) continue;
                        
                        Array.set(arrayWithObjectRemoved, addIndex++, Array.get(removeFromArray, lcv));
                    }
                    
                }
                
                if (rootForDeletion == null) return null;
                
                if (changeList) {
                    if (xmlDynamicChange.getBeanDatabase() != null) {
                        myParent.changeInHub(childProperty, arrayWithObjectRemoved, xmlDynamicChange.getBeanDatabase());
                    }
                
                    myParent._setProperty(childProperty, arrayWithObjectRemoved, false, true);
                }
            }
        }
        else {
            // Direct child
            rootForDeletion = (BaseHK2JAXBBean) myParent._getProperty(childProperty);
            if (rootForDeletion == null) return null;
            
            if (xmlDynamicChange.getBeanDatabase() != null) {
                myParent.changeInHub(childProperty, null, xmlDynamicChange.getBeanDatabase());
            }
            
            myParent._setProperty(childProperty, null, false, true);
        }
        
        // Need to get all the beans to delete
        invokeAllDeletedChangeListeners(changeInformation, rootForDeletion, myParent._getClassReflectionHelper());
        
        if (xmlDynamicChange.getDynamicConfiguration() != null) {
            HashSet> descriptorsToRemove = new HashSet>();
            
            getDescriptorsToRemove(rootForDeletion, descriptorsToRemove);
            
            for (ActiveDescriptor descriptorToRemove : descriptorsToRemove) {
                xmlDynamicChange.getDynamicConfiguration().addUnbindFilter(BuilderHelper.createSpecificDescriptorFilter(descriptorToRemove));
            }
        }
        
        if (xmlDynamicChange.getBeanDatabase() != null) {
            String rootXmlPath = rootForDeletion._getXmlPath();
            String rootInstanceName = rootForDeletion._getInstanceName();
            
            WriteableType rootType = xmlDynamicChange.getBeanDatabase().getWriteableType(rootXmlPath);
            if (rootType != null) {
                rootType.removeInstance(rootInstanceName);
            
                String typeRemovalIndicator = rootXmlPath + BaseHK2JAXBBean.XML_PATH_SEPARATOR;
            
                Set allTypes = xmlDynamicChange.getBeanDatabase().getAllWriteableTypes();
                for (WriteableType allType : allTypes) {
                    if (allType.getName().startsWith(typeRemovalIndicator)) {
                        
                        Map allInstances = allType.getInstances();
                        
                        Set removeMe = new LinkedHashSet();
                        
                        // Do not forget to put the dot at the end or it will remove too much
                        String rootInstancePrefix = rootInstanceName + ".";
                        for (String iKey : allInstances.keySet()) {
                            if (!iKey.startsWith(rootInstancePrefix)) continue;
                            removeMe.add(iKey);
                        }
                        
                        for (String iKey : removeMe) {
                            allType.removeInstance(iKey);
                        }
                    }
                }
            }
        }
        
        return rootForDeletion;
    }
    
    @SuppressWarnings("unchecked")
    private static void getDescriptorsToRemove(BaseHK2JAXBBean fromMe, HashSet> descriptorsToRemove) {
        ActiveDescriptor fromMeDescriptor = fromMe._getSelfDescriptor();
        if (fromMeDescriptor == null) return;
        
        descriptorsToRemove.add(fromMeDescriptor);
        
        ModelImpl model = fromMe._getModel();
        if (model == null) return;
        
        for (ParentedModel parentedChild : model.getAllChildren()) {
            String childPropertyName = parentedChild.getChildXmlTag();
            
            switch (parentedChild.getChildType()) {
            case LIST:
                List listChildren = (List) fromMe._getProperty(childPropertyName);
                if (listChildren != null) {
                    for (BaseHK2JAXBBean listChild : listChildren) {
                        getDescriptorsToRemove(listChild, descriptorsToRemove);
                    }
                }
                break;
            case ARRAY:
                Object arrayChildren = fromMe._getProperty(childPropertyName);
                if (arrayChildren != null) {
                    int arrayLength = Array.getLength(arrayChildren);
                    
                    for (int lcv = 0; lcv < arrayLength; lcv++) {
                        BaseHK2JAXBBean bean = (BaseHK2JAXBBean) Array.get(arrayChildren, lcv);
                        getDescriptorsToRemove(bean, descriptorsToRemove);
                    }
                }
                break;
            case DIRECT:
                BaseHK2JAXBBean bean = (BaseHK2JAXBBean) fromMe._getProperty(childPropertyName);
                if (bean != null) {
                    getDescriptorsToRemove(bean, descriptorsToRemove);
                }
                break;
            default:
                throw new AssertionError("Unknown child type " + parentedChild.getChildType());
            }
            
        }
    }
    
    private final static Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
    private final static Byte DEFAULT_BYTE = new Byte((byte) 0);
    private final static Character DEFAULT_CHARACTER = new Character((char) 0);
    private final static Short DEFAULT_SHORT = new Short((short) 0);
    private final static Integer DEFAULT_INTEGER = new Integer(0);
    private final static Long DEFAULT_LONG = new Long(0L);
    private final static Float DEFAULT_FLOAT = new Float(0);
    private final static Double DEFAULT_DOUBLE = new Double((double) 0);
    
    /**
     * Returns the default value given the string version of the default and
     * the expected result (non-child properties)
     * 
     * @param givenStringDefault
     * @param expectedClass
     * @return
     */
    public static Object getDefaultValue(String givenStringDefault, Class expectedClass) {
        if (givenStringDefault == null || JAUtilities.JAXB_DEFAULT_DEFAULT.equals(givenStringDefault)) {
            if (int.class.equals(expectedClass)) {
                return DEFAULT_INTEGER;
            }
            if (long.class.equals(expectedClass)) {
                return DEFAULT_LONG;
            }
            if (boolean.class.equals(expectedClass)) {
                return DEFAULT_BOOLEAN;
            }
            if (short.class.equals(expectedClass)) {
                return DEFAULT_SHORT;
            }
            if (byte.class.equals(expectedClass)) {
                return DEFAULT_BYTE;
            }
            if (char.class.equals(expectedClass)) {
                return DEFAULT_CHARACTER;
            }
            if (float.class.equals(expectedClass)) {
                return DEFAULT_FLOAT;
            }
            if (double.class.equals(expectedClass)) {
                return DEFAULT_DOUBLE;
            }
            
            return null;
        }
        
        if (String.class.equals(expectedClass)) {
            return givenStringDefault;
        }
        if (int.class.equals(expectedClass)) {
            return Integer.parseInt(givenStringDefault);
        }
        if (long.class.equals(expectedClass)) {
            return Long.parseLong(givenStringDefault);
        }
        if (boolean.class.equals(expectedClass)) {
            return Boolean.parseBoolean(givenStringDefault);
        }
        if (short.class.equals(expectedClass)) {
            return Short.parseShort(givenStringDefault);
        }
        if (byte.class.equals(expectedClass)) {
            return Byte.parseByte(givenStringDefault);
        }
        if (char.class.equals(expectedClass)) {
            return givenStringDefault.charAt(0);
        }
        if (float.class.equals(expectedClass)) {
            return Float.parseFloat(givenStringDefault);
        }
        if (double.class.equals(expectedClass)) {
            return Double.parseDouble(givenStringDefault);
        }
        if (expectedClass.isArray() && byte.class.equals(expectedClass.getComponentType())) {
            return givenStringDefault.getBytes();
            // return DatatypeConverter.parseHexBinary(givenStringDefault);
        }
        
        throw new AssertionError("Default for type " + expectedClass.getName() + " not implemented");
    }
    
    public static void fillInUnfinishedReferences(Map referenceMap,
            List unresolved) {
        List errors = new LinkedList();
        
        for (UnresolvedReference unresolvedRef : unresolved) {
            ReferenceKey key = new ReferenceKey(unresolvedRef.getType(), unresolvedRef.getXmlID());
            BaseHK2JAXBBean reference = referenceMap.get(key);
            if (reference == null) {
                errors.add(new IllegalStateException("No Reference was found for " + unresolvedRef));
            }
            
            BaseHK2JAXBBean unfinished = unresolvedRef.getUnfinished();
            unfinished._setProperty(unresolvedRef.getPropertyName(), reference);
        }
        
        if (!errors.isEmpty()) {
            throw new MultiException(errors);
        }
    }
    
    public static Method findSuitableCustomizerMethod(Class cClass, String methodName, Class[] params, Class topInterface) {
        try {
            return cClass.getMethod(methodName, params);
        }
        catch (NoSuchMethodException nsme) {
            // Go on
        }
        
        if (topInterface == null) return null;
        
        {
            // First look for an exact match which should be prioritized over others
            int altParamsLength = params.length + 1;
            Class exactParams[] = new Class[altParamsLength];
            
            exactParams[0] = topInterface;
            for (int lcv = 0; lcv < params.length; lcv++) {
                exactParams[lcv+1] = params[lcv];
            }
            
            try {
                return cClass.getMethod(methodName, exactParams);
            }
            catch (NoSuchMethodException nsme) {
                // Go on
            }
        }
        
        
        for (Method candidate : cClass.getMethods()) {
            if (!methodName.equals(candidate.getName())) continue;
            
            int altParamsLength = params.length + 1;
            Class candidateParams[] = candidate.getParameterTypes();
            
            if (candidateParams.length != altParamsLength) continue;
            
            if (!candidateParams[0].isAssignableFrom(topInterface)) continue;
            
            // Now check all the other params
            boolean found = true;
            for (int lcv = 1; lcv < altParamsLength; lcv++) {
                if (!candidateParams[lcv].equals(params[lcv - 1])) {
                    found = false;
                    break;
                }
            }
            if (!found) continue;
            
            return candidate;
            
        }
        
        return null;
    }
    
    @SuppressWarnings("unchecked")
    private static void invokeAllDeletedChangeListeners(DynamicChangeInfo control,
            BaseHK2JAXBBean rootBean,
            ClassReflectionHelper helper) {
        ModelImpl model = rootBean._getModel();
        
        Map childrenByName = model.getChildrenByName();
        for (Map.Entry entry : childrenByName.entrySet()) {
            String propertyName = entry.getKey();
            ParentedModel parentModel = entry.getValue();
            
            Object child = rootBean._getProperty(propertyName);
            if (child == null) continue;
            
            if (ChildType.LIST.equals(parentModel.getChildType())) {
                List listChildren = (List) child;
                
                for (BaseHK2JAXBBean grandchild : listChildren) {
                    invokeAllDeletedChangeListeners(control, grandchild, helper);
                }
            }
            else if (ChildType.ARRAY.equals(parentModel.getChildType())) {
                int length = Array.getLength(child);
                
                for (int lcv = 0; lcv < length; lcv++) {
                    BaseHK2JAXBBean grandchild = (BaseHK2JAXBBean) Array.get(child, lcv);
                    invokeAllDeletedChangeListeners(control, grandchild, helper);
                }
            }
            else if (ChildType.DIRECT.equals(parentModel.getChildType())) {
                BaseHK2JAXBBean grandchild = (BaseHK2JAXBBean) child;
                
                invokeAllDeletedChangeListeners(control, grandchild, helper);
            }
        }
        
        invokeVetoableChangeListeners(control, rootBean, rootBean, null, EMPTY_STRING, helper);
    }
    
    @SuppressWarnings("unchecked")
    public static void invokeVetoableChangeListeners(DynamicChangeInfo control,
            BaseHK2JAXBBean source,
            Object oldValue,
            Object newValue,
            String propertyName,
            ClassReflectionHelper helper) {
        if (control == null) return;
        
        Validator validator = control.findValidator();
        if (validator != null) {
            ModelImpl model = source._getModel();
            
            String javaName = model.getJavaNameFromKey(propertyName, helper);
            
            if (javaName != null) {
                Set> violations =
                    validator.validateValue((Class) source.getClass(), javaName, newValue);
                if (violations != null && !violations.isEmpty()) {
                    throw new MultiException(new ConstraintViolationException(violations));
                }
            }
        }
        
        List vetoers = control.getChangeListeners();
            
        PropertyChangeEvent event = new PropertyChangeEvent(source,
                    propertyName, oldValue, newValue);
            
        List errors = new LinkedList();
        for (VetoableChangeListener listener : vetoers) {
            try {
                listener.vetoableChange(event);
            }
            catch (PropertyVetoException pve) {
                // In this case we do NOT run the subsequent listeners
                errors.add(pve);
                throw new MultiException(errors);
            }
            catch (Throwable th) {
                // In this case we DO run the subsequent listeners but will
                // report all the errors in the end
                errors.add(th);
            }
        }
        if (!errors.isEmpty()) {
            throw new MultiException(errors);
        }
    }
    
    /**
     * Must have write lock of source held though this is only doing reading
     * 
     * @param classReflectionHelper
     * @param source
     * @param other
     * @return
     */
    public static Differences getDiff(BaseHK2JAXBBean source,
            BaseHK2JAXBBean other) {
        ModelImpl sourceModel = source._getModel();
        ModelImpl otherModel = other._getModel();
        
        if (!sourceModel.equals(otherModel)) {
            throw new AssertionError("Can only diff two beans of the same type.  Source is " + sourceModel + " other is " + otherModel);
        }
        
        Differences retVal = new Differences();
        
        getAllDifferences(source, other, retVal);
        
        return retVal;
    }
    
    @SuppressWarnings("unchecked")
    private static void getAllDifferences(BaseHK2JAXBBean source,
            BaseHK2JAXBBean other,
            Differences differences) {
        Difference localDifference = new Difference(source);
        
        ModelImpl sourceModel = source._getModel();
        
        Map sourceMap = source._getBeanLikeMap();
        Map otherMap = other._getBeanLikeMap();
        
        Map nonChildProperties = sourceModel.getNonChildProperties();
        
        for (Map.Entry nonChildPropertyEntry : nonChildProperties.entrySet()) {
            String nonChildProperty = nonChildPropertyEntry.getKey();
            ChildDataModel dataModel = nonChildPropertyEntry.getValue();
                    
            Object sourceValue = sourceMap.get(nonChildProperty);
            Object otherValue = otherMap.get(nonChildProperty);
            
            if (!dataModel.isReference()) {
                if (!GeneralUtilities.safeEquals(sourceValue, otherValue)) {
                    localDifference.addNonChildChange(new PropertyChangeEvent(source, nonChildProperty, sourceValue, otherValue));
                }
            }
            else {
                // Comparing references
                if (sourceValue != null && otherValue == null) {
                    localDifference.addNonChildChange(new PropertyChangeEvent(source, nonChildProperty, sourceValue, otherValue));
                }
                else if (sourceValue == null && otherValue != null) {
                    localDifference.addNonChildChange(new PropertyChangeEvent(source, nonChildProperty, sourceValue, otherValue));
                }
                else if (sourceValue != null) {
                    BaseHK2JAXBBean sourceReference = (BaseHK2JAXBBean) sourceValue;
                    BaseHK2JAXBBean otherReference = (BaseHK2JAXBBean) otherValue;
                    
                    String sourceReferenceKey = sourceReference._getKeyValue();
                    String otherReferenceKey = otherReference._getKeyValue();
                    
                    if (!GeneralUtilities.safeEquals(sourceReferenceKey, otherReferenceKey)) {
                        localDifference.addNonChildChange(new PropertyChangeEvent(source, nonChildProperty, sourceValue, otherValue));
                    }
                    
                }
                
            }
        }
        
        Map childProperties = sourceModel.getChildrenByName();
        for (Map.Entry childEntry : childProperties.entrySet()) {
            String xmlTag = childEntry.getKey();
            ParentedModel pModel = childEntry.getValue();
            
            Object sourceValue = sourceMap.get(xmlTag);
            Object otherValue = otherMap.get(xmlTag);
            
            if (ChildType.DIRECT.equals(pModel.getChildType())) {
                if (sourceValue == null && otherValue != null) {
                    // This is just a pure add
                    localDifference.addAdd(xmlTag, (BaseHK2JAXBBean) otherValue, -1); 
                }
                else if (sourceValue != null && otherValue == null) {
                    // A pure remove
                    localDifference.addRemove(xmlTag, new RemoveData(xmlTag, (BaseHK2JAXBBean) sourceValue));
                }
                else if (sourceValue != null) {
                    getAllDifferences((BaseHK2JAXBBean) sourceValue, (BaseHK2JAXBBean) otherValue, differences);
                }
            }
            else if (ChildType.LIST.equals(pModel.getChildType())) {
                String keyProperty = pModel.getChildModel().getKeyProperty();
                
                List sourceValueList = (List) sourceValue;
                List otherValueList = (List) otherValue;
                
                if (sourceValueList == null) sourceValueList = Collections.emptyList();
                if (otherValueList == null) otherValueList = Collections.emptyList();
                
                if (keyProperty != null) {
                    for (BaseHK2JAXBBean sourceBean : sourceValueList) {
                        String sourceKeyValue = sourceBean._getKeyValue();
                        
                        Object otherBean = other._lookupChild(xmlTag, sourceKeyValue);
                        if (otherBean == null) {
                            // Removing this bean
                            localDifference.addRemove(xmlTag, new RemoveData(xmlTag, sourceKeyValue, sourceBean));
                        }
                        else {
                            // Need to know sub-differences
                            getAllDifferences(sourceBean, (BaseHK2JAXBBean) otherBean, differences);
                        }
                    }
                    
                    for (BaseHK2JAXBBean otherBean : otherValueList) {
                        String otherKeyValue = otherBean._getKeyValue();
                        
                        Object addMe = source._lookupChild(xmlTag, otherKeyValue);
                        if (addMe == null) {
                            // Adding this bean
                            localDifference.addAdd(xmlTag, otherBean, -1);
                        }
                    }
                }
                else {
                    // Both lists are there, this is an unkeyed list, we go *purely* on list size
                    UnkeyedDiff unkeyedDiff = new UnkeyedDiff(sourceValueList, otherValueList, source, pModel);
                    Differences unkeyedDiffs = unkeyedDiff.compute();
                    
                    differences.merge(unkeyedDiffs);
                }
            }
            else if (ChildType.ARRAY.equals(pModel.getChildType())) {
                String keyProperty = pModel.getChildModel().getKeyProperty();
                
                Object sourceArray = (sourceValue == null) ? new BaseHK2JAXBBean[0] : sourceValue ;
                Object otherArray = (otherValue == null) ? new BaseHK2JAXBBean[0] : otherValue;
                
                if (keyProperty != null) {
                    int sourceLength = Array.getLength(sourceArray);
                    
                    for (int lcv = 0; lcv < sourceLength; lcv++) {
                        BaseHK2JAXBBean sourceBean = (BaseHK2JAXBBean) Array.get(sourceArray, lcv);
                        
                        String sourceKeyValue = sourceBean._getKeyValue();
                        
                        Object otherBean = other._lookupChild(xmlTag, sourceKeyValue);
                        if (otherBean == null) {
                            // Removing this bean
                            localDifference.addRemove(xmlTag, new RemoveData(xmlTag, sourceKeyValue, sourceBean));
                        }
                        else {
                            // Need to know sub-differences
                            getAllDifferences(sourceBean, (BaseHK2JAXBBean) otherBean, differences);
                        }
                    }
                    
                    int otherLength = Array.getLength(otherArray);
                    
                    for (int lcv = 0; lcv < otherLength; lcv++) {
                        BaseHK2JAXBBean otherBean = (BaseHK2JAXBBean) Array.get(otherArray, lcv);
                        
                        String otherKeyValue = otherBean._getKeyValue();
                        
                        Object addMe = source._lookupChild(xmlTag, otherKeyValue);
                        if (addMe == null) {
                            // Adding this bean
                            localDifference.addAdd(xmlTag, otherBean, -1);
                        }
                    }
                }
                else {  
                    // Both lists are there, this is an unkeyed list
                    UnkeyedDiff unkeyedDiff = new UnkeyedDiff((Object[]) sourceArray, (Object[]) otherArray, source, pModel);
                    Differences unkeyedDiffs = unkeyedDiff.compute();
                    
                    differences.merge(unkeyedDiffs);
                }
            }
        }
        
        if (localDifference.isDirty()) {
            differences.addDifference(localDifference);
        }
    }
    
    /**
     * Must have write lock of source held
     * 
     * @param classReflectionHelper
     * @param source
     * @param other
     * @return
     */
    public static void applyDiff(Differences differences, DynamicChangeInfo changeControl) {
        for (Difference difference : differences.getDifferences()) {
            BaseHK2JAXBBean source = difference.getSource();
            
            List allSourceChanges = new LinkedList();
            allSourceChanges.addAll(difference.getNonChildChanges());
            
            if (!difference.hasChildChanges()) {
                applyAllSourceChanges(source, allSourceChanges, changeControl);
                continue;
            }
            
            ModelImpl model = source._getModel();
            
            for (Map.Entry childEntry : difference.getChildChanges().entrySet()) {
                String xmlKey = childEntry.getKey();
                AddRemoveMoveDifference childDiffs = childEntry.getValue();
                ParentedModel parentedModel = model.getChild(xmlKey);
                ChildType childType = parentedModel.getChildType();
                
                boolean changeList = ChildType.DIRECT.equals(childType);
                
                Object oldListOrArray = source._getProperty(xmlKey);
                Map arrayChanges = null;
                
                if (!changeList) {
                  arrayChanges = new LinkedHashMap();
                }
                
                for (AddData added : childDiffs.getAdds()) {
                    BaseHK2JAXBBean addMe = added.getToAdd();
                    int index = added.getIndex();
                
                    BaseHK2JAXBBean addedBean = (BaseHK2JAXBBean) source._doAdd(xmlKey, addMe, null, index, changeList);
                    if (!changeList) {
                        arrayChanges.put(index, addedBean);
                    }
                    else {
                        allSourceChanges.add(new PropertyChangeEvent(source, xmlKey, null, addedBean));
                    }
                }
                
                for (RemoveData removed : childDiffs.getRemoves()) {
                    source._doRemove(xmlKey, removed.getChildKey(), removed.getIndex(), removed.getChild(), changeList);
                    if (changeList) {
                        allSourceChanges.add(new PropertyChangeEvent(source, xmlKey, oldListOrArray, null));
                    }
                }
                
                for (MoveData md : childDiffs.getMoves()) {
                    BaseHK2JAXBBean movedBean = getLOABean(oldListOrArray, childType, md.getOldIndex());
                    
                    if (!changeList) {
                        arrayChanges.put(md.getNewIndex(), movedBean);
                    }
                }
                
                if (!changeList) {
                    int newSize = childDiffs.getNewSize(getLOASize(oldListOrArray, childType));
                    Object newListOrArray = createLOA(childType, newSize, parentedModel.getChildModel());
                    
                    for (int lcv = 0; lcv < newSize; lcv++) {
                        BaseHK2JAXBBean toPut = arrayChanges.get(lcv);
                        
                        if (toPut == null) {
                            toPut = getLOABean(oldListOrArray, childType, lcv);
                        }
                        
                        putLOABean(newListOrArray, childType, lcv, toPut);
                    }
                    
                    allSourceChanges.add(new PropertyChangeEvent(source, xmlKey, oldListOrArray, newListOrArray));
                }
            }
            
            applyAllSourceChanges(source, allSourceChanges, changeControl);
        }
    }
    
    private static void applyAllSourceChanges(BaseHK2JAXBBean source, List events, DynamicChangeInfo changeControl) {
        boolean success = false;
        XmlDynamicChange xmlDynamicChange = changeControl.startOrContinueChange(source);
        try {
            WriteableBeanDatabase wbd = xmlDynamicChange.getBeanDatabase();
            
            boolean madeAChange = false;
            for (PropertyChangeEvent pce : events) {
                if (!GeneralUtilities.safeEquals(pce.getOldValue(), pce.getNewValue())) {
                    madeAChange = true;
                    Utilities.invokeVetoableChangeListeners(changeControl,
                            source,
                            pce.getOldValue(),
                            pce.getNewValue(),
                            pce.getPropertyName(),
                            source._getClassReflectionHelper());
                }
            }
            
            if (!madeAChange) {
                success = true;
                
                return;
            }
            
            if (wbd != null) {
                source.changeInHub(events, wbd);
            }
            
            for (PropertyChangeEvent pce : events) { 
                source._setProperty(pce.getPropertyName(), pce.getNewValue(), false, true);
            }
            
            success = true;
        }
        finally {
            changeControl.endOrDeferChange(success);
        }
    }
    
    private static Object createLOA(ChildType type, int size, ModelImpl childModel) {
        if (ChildType.ARRAY.equals(type)) {
            return Array.newInstance(childModel.getOriginalInterfaceAsClass(), size);
        }
        
        return new ArrayList(size);
    }
    
    @SuppressWarnings("unchecked")
    private static void putLOABean(Object listOrArray, ChildType type, int index, BaseHK2JAXBBean putMe) {
        if (ChildType.ARRAY.equals(type)) {
            Array.set(listOrArray, index, putMe);
            return;
        }

        List list = (List) listOrArray;
            
        list.add(index, putMe);
    }
    
    @SuppressWarnings("unchecked")
    private static BaseHK2JAXBBean getLOABean(Object listOrArray, ChildType type, int index) {
        if (ChildType.ARRAY.equals(type)) {
            return (BaseHK2JAXBBean) Array.get(listOrArray, index);
        }
        
        if (ChildType.LIST.equals(type)) {
            List list = (List) listOrArray;
            
            return list.get(index);
        }
        
        return (BaseHK2JAXBBean) listOrArray;
        
    }
    
    // LOA stands for List Or Array
    @SuppressWarnings("unchecked")
    private static int getLOASize(Object listOrArray, ChildType type) {
        if (ChildType.ARRAY.equals(type)) {
            return Array.getLength(listOrArray);
        }
        if (ChildType.LIST.equals(type)) {
            List list = (List) listOrArray;
            
            return list.size();
        }
        
        // Direct is always size 1
        return 1;
    }
    
    /**
     * Calculates and sets the add cost for the given bean
     * and sets all add costs for this bean and all its
     * children
     * 
     * @param bean The bean to calculate and set the
     * add cost on
     * @return The add cost, which will be at least one,
     * or -1 if the bean is null
     */
    @SuppressWarnings("unchecked")
    public static int calculateAddCost(BaseHK2JAXBBean bean) {
        if (bean == null) return -1;
        
        int knownValue = bean.__getAddCost();
        if (knownValue >= 0) return knownValue;
        
        int retVal = 1;
        
        ModelImpl model = bean._getModel();
        for (ParentedModel parentedModel : model.getAllChildren()) {
            String propName = parentedModel.getChildXmlTag();
            
            Object rawChild = bean._getProperty(propName);
            if (rawChild == null) continue;
            
            switch (parentedModel.getChildType()) {
            case DIRECT:
                {
                    BaseHK2JAXBBean child = (BaseHK2JAXBBean) rawChild;
                    int childCost = calculateAddCost(child);
                
                    retVal += childCost;
                }
                break;
            case LIST:
                List childList = (List) rawChild;
                for (BaseHK2JAXBBean child : childList) {
                    int childCost = calculateAddCost(child);
                    
                    retVal += childCost;
                }
                break;
            case ARRAY:
                int length = Array.getLength(rawChild);
                for (int lcv = 0; lcv < length; lcv++) {
                    BaseHK2JAXBBean child = (BaseHK2JAXBBean) Array.get(rawChild, lcv);
                    int childCost = calculateAddCost(child);
                    
                    retVal += childCost;
                }
                break;
            default:
                throw new AssertionError("Unknown child type " + parentedModel.getChildType());
            }
        }
        
        bean.__setAddCost(retVal);
        return retVal;
    }
}