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

org.glassfish.hk2.xml.jaxb.internal.BaseHK2JAXBBean Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2014-2016 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.jaxb.internal;

import java.beans.PropertyChangeEvent;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.glassfish.hk2.api.ActiveDescriptor;
import org.glassfish.hk2.api.Customizer;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.configuration.hub.api.WriteableBeanDatabase;
import org.glassfish.hk2.configuration.hub.api.WriteableType;
import org.glassfish.hk2.utilities.general.GeneralUtilities;
import org.glassfish.hk2.utilities.reflection.BeanReflectionHelper;
import org.glassfish.hk2.utilities.reflection.ClassReflectionHelper;
import org.glassfish.hk2.utilities.reflection.Logger;
import org.glassfish.hk2.utilities.reflection.ReflectionHelper;
import org.glassfish.hk2.xml.api.XmlHk2ConfigurationBean;
import org.glassfish.hk2.xml.api.XmlRootHandle;
import org.glassfish.hk2.xml.internal.ChildDataModel;
import org.glassfish.hk2.xml.internal.ChildType;
import org.glassfish.hk2.xml.internal.DynamicChangeInfo;
import org.glassfish.hk2.xml.internal.ModelImpl;
import org.glassfish.hk2.xml.internal.ParentedModel;
import org.glassfish.hk2.xml.internal.Utilities;
import org.glassfish.hk2.xml.internal.XmlDynamicChange;
import org.glassfish.hk2.xml.internal.XmlRootHandleImpl;

/**
 * @author jwells
 *
 */
public abstract class BaseHK2JAXBBean implements XmlHk2ConfigurationBean, Serializable {
    private static final long serialVersionUID = 8149986319033910297L;

    private final static boolean DEBUG_GETS_AND_SETS = AccessController.doPrivileged(new PrivilegedAction() {
        @Override
        public Boolean run() {
            return Boolean.parseBoolean(
                System.getProperty("org.jvnet.hk2.properties.xmlservice.jaxb.getsandsets", "false"));
        }
            
    });
    
    private final static String EMPTY = "";
    public final static char XML_PATH_SEPARATOR = '/';
    
    /**
     * All fields, including child lists and direct children
     */
    private HashMap beanLikeMap = new HashMap();
    
    private HashMap backupMap = null;
    
    /**
     * All children whose type has an identifier.  First key is the xml parameter name, second
     * key is the identifier of the specific child.  Used in lookup operations.  Works
     * as a cache, may not be completely accurate and must be flushed on remove
     * operations
     */
    private final Map> keyedChildrenCache = new HashMap>();
    
    /** The model for this, including lists of all children property names */
    // private UnparentedNode model;
    
    /** The parent of this instance, or null if this is a root (or has not been fully initialized yet) */
    private Object parent;
    
    /** My own XmlTag, which is determined either by my parent or by my root value */
    private String selfXmlTag;
    
    /** The full instance name this takes, with names from keyed children or ids from unkeyed multi children */
    private String instanceName;
    
    /** The value of my key field, if I have one */
    private String keyValue;
    
    /** The global classReflectionHelper, which minimizes reflection */
    private ClassReflectionHelper classReflectionHelper;
    
    /** My own full xmlPath from root */
    private String xmlPath = EMPTY;
    
    /**
     * This object contains the tree locks
     * Once this has been set then all other fields should have been set,
     * and at that point this object is ready for its life as an
     * in-memory node in a tree hierarchy
     */
    private volatile transient DynamicChangeInfo changeControl;
    
    /**
     * The root of this bean, or null if there is no root
     * for this bean
     */
    private volatile transient XmlRootHandleImpl root;
    
    /**
     * If true this bean has been given to the user to
     * add/remove or call getters or setters.  This can
     * happen in two ways, the first being via being parsed
     * from an XML file, the other is via dynamic creation
     */
    private boolean active = false;
    
    /**
     * The descriptor that this bean is advertised with
     */
    private transient ActiveDescriptor selfDescriptor;
    
    /**
     * For JAXB and Serialization
     */
    public BaseHK2JAXBBean() {
    }
    
    public void _setProperty(String propName, Object propValue) {
        _setProperty(propName, propValue, true);
    }
    
    public void _setProperty(String propName, Object propValue, boolean changeInHub) {
        _setProperty(propName, propValue, changeInHub, false);
    }
    
    @SuppressWarnings("unchecked")
    public void _setProperty(String propName, Object propValue, boolean changeInHub, boolean rawSet) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        if (DEBUG_GETS_AND_SETS) {
            // Hidden behind static because of potential expensive toString costs
            Logger.getLogger().debug("XmlService setting property " + propName + " to " + propValue + " in " + this + " rawSet=" + rawSet);
        }
        
        if (propValue != null && (propValue instanceof List)) {
            // All lists are unmodifiable and ArrayLists
            if (propValue instanceof ArrayList) {
                propValue = Collections.unmodifiableList((ArrayList) propValue);
            }
            else {
                propValue = Collections.unmodifiableList(new ArrayList((List) propValue));
            }
        }
        
        if (changeControl == null) {
            if (active) {
                synchronized (this) {
                    beanLikeMap.put(propName, propValue);
                }
            }
            else {
                beanLikeMap.put(propName, propValue);
            }
        }
        else {
            boolean doAdd = false;
            boolean doRemove = false;
            boolean doModify = false;
            Object currentValue = null;
            
            if (!rawSet) {
                changeControl.getReadLock().lock();
                try {
                    currentValue = beanLikeMap.get(propName);
                
                    // If both null this goes, or if they are somehow exactly the same
                    if (currentValue == propValue) return;
                
                    ModelImpl model = _getModel();
                    ParentedModel childModel = model.getChild(propName);
                    if (childModel != null) {
                        if (ChildType.DIRECT.equals(childModel.getChildType())) {
                            if (currentValue == null && propValue != null) {
                                // This is an add
                                doAdd = true;
                            }
                            else if (currentValue != null && propValue == null) {
                                // This is a remove
                                doRemove = true;
                            }
                            else {
                                // Both beans are different
                                doModify = true;
                            }
                        }
                        else {
                            // Just not going to work
                            doModify = true;
                        }
                    }
                }
                finally {
                    changeControl.getReadLock().unlock();
                }
            }
            
            if (doAdd) {
                _doAdd(propName, propValue, null, -1);
                return;
            }
            if (doRemove) {
                _doRemove(propName, null, -1, currentValue);
                return;
            }
            if (doModify) {
                throw new IllegalStateException(
                        "A bean may not be modified with a set method, instead directly manipulate the fields of the existing bean or use add and remove methods");
            }
            
            changeControl.getWriteLock().lock();
            try {
                changeControl.startOrContinueChange(this);
                boolean success = false;
                
                try {
                    Utilities.invokeVetoableChangeListeners(changeControl, this,
                        beanLikeMap.get(propName), propValue, propName, classReflectionHelper);
                
                    if (changeInHub) {
                        changeInHubDirect(propName, propValue);
                    }
                
                    if (backupMap == null) {
                        backupMap = new HashMap(beanLikeMap);
                    }
                
                    beanLikeMap.put(propName, propValue);
                    
                    success = true;
                }
                finally {
                    changeControl.endOrDeferChange(success);
                }
            }
            finally {
                changeControl.getWriteLock().unlock();
            }
        }
    }
    
    public void _setProperty(String propName, byte propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Byte) propValue);
    }
    
    public void _setProperty(String propName, boolean propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Boolean) propValue);
    }
    
    public void _setProperty(String propName, char propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Character) propValue);
    }
    
    public void _setProperty(String propName, short propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Short) propValue);
    }
    
    public void _setProperty(String propName, int propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Integer) propValue);
    }
    
    public void _setProperty(String propName, float propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Float) propValue);
    }
    
    public void _setProperty(String propName, long propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Long) propValue);
    }
    
    public void _setProperty(String propName, double propValue) {
        if (propName == null) throw new IllegalArgumentException("properyName may not be null");
        
        _setProperty(propName, (Double) propValue);
    }
    
    private Object _getProperty(String propName, Class expectedClass) {
        return _getProperty(propName, expectedClass, null);
    }
    
    private Object _getProperty(String propName, Class expectedClass, ParentedModel parentNode) {
        boolean isSet;
        Object retVal;
        boolean doDefaulting = active ? true : false;
        
        if (changeControl == null) {
            if (active) {
                synchronized (this) {
                    isSet = beanLikeMap.containsKey(propName);
                    retVal = beanLikeMap.get(propName);
                }
            }
            else {
                isSet = beanLikeMap.containsKey(propName);
                retVal = beanLikeMap.get(propName);
            }
        }
        else {
            changeControl.getReadLock().lock();
            try {
                doDefaulting = true;
                isSet = beanLikeMap.containsKey(propName);
                retVal = beanLikeMap.get(propName);
            }
            finally {
                changeControl.getReadLock().unlock();
            }
        }
        
        if (doDefaulting && (retVal == null) && !isSet) {
            if (expectedClass != null) {
                retVal = Utilities.getDefaultValue(_getModel().getDefaultChildValue(propName), expectedClass);
            }
            else if (parentNode != null) {
                switch (parentNode.getChildType()) {
                case LIST:
                    retVal = Collections.EMPTY_LIST;
                    break;
                case ARRAY:
                    Class cType = parentNode.getChildModel().getOriginalInterfaceAsClass();
                    retVal = Array.newInstance(cType, 0);
                    break;
                case DIRECT:
                default:
                    break;
                
                }
                
            }
        }
        
        if (DEBUG_GETS_AND_SETS) {
            // Hidden behind static because of potential expensive toString costs
            Logger.getLogger().debug("XmlService getting property " + propName + "=" + retVal + " in " + this);
        }
        
        return retVal;
    }
    
    /**
     * Called by proxy
     * 
     * @param propName Property of child or non-child element or attribute
     * @return Value
     */
    public Object _getProperty(String propName) {
        if (!_getModel().isChildProperty(propName)) {
            return _getProperty(propName, _getModel().getNonChildType(propName));
        }
        
        ParentedModel parent = _getModel().getChild(propName);
        return _getProperty(propName, null, parent);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public boolean _getPropertyZ(String propName) {
        return (Boolean) _getProperty(propName, boolean.class);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public byte _getPropertyB(String propName) {
        return (Byte) _getProperty(propName, byte.class);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public char _getPropertyC(String propName) {
        return (Character) _getProperty(propName, char.class);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public short _getPropertyS(String propName) {
        return (Short) _getProperty(propName, short.class);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public int _getPropertyI(String propName) {
        return (Integer) _getProperty(propName, int.class);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public float _getPropertyF(String propName) {
        return (Float) _getProperty(propName, float.class);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public long _getPropertyJ(String propName) {
        return (Long) _getProperty(propName, long.class);
    }
    
    /**
     * Called by proxy
     * 
     * @param propName
     * @return
     */
    public double _getPropertyD(String propName) {
        return (Double) _getProperty(propName, double.class);
    }
    
    @SuppressWarnings("unchecked")
    private Object internalLookup(String propName, String keyValue) {
        // First look in the cache
        Object retVal = null;
        
        Map byName = keyedChildrenCache.get(propName);
        if (byName != null) {
            retVal = byName.get(keyValue);
        }
        
        if (retVal != null) {
            // Found it in cache!
            return retVal;
        }
        
        // Now do it the hard way
        Object prop = _getProperty(propName);
        if (prop == null) return null;  // Just not found
        
        if (prop instanceof List) {
            for (BaseHK2JAXBBean child : (List) prop) {
                if (GeneralUtilities.safeEquals(keyValue, child._getKeyValue())) {
                    // Add it to the cache
                    if (byName == null) {
                        byName = new HashMap();
                        
                        keyedChildrenCache.put(propName, byName);
                    }
                    
                    byName.put(keyValue, child);
                    
                    // and return
                    return child;
                }
            }
        }
        else if (prop.getClass().isArray()) {
            for (Object childRaw : (Object[]) prop) {
                BaseHK2JAXBBean child = (BaseHK2JAXBBean) childRaw;
                
                if (GeneralUtilities.safeEquals(keyValue, child._getKeyValue())) {
                    // Add it to the cache
                    if (byName == null) {
                        byName = new HashMap();
                        
                        keyedChildrenCache.put(propName, byName);
                    }
                    
                    byName.put(keyValue, child);
                    
                    // and return
                    return child;
                }
            }
        }
        
        // Just not found
        return null;
    }
    
    public Object _lookupChild(String propName, String keyValue) {
        if (changeControl == null) {
            return internalLookup(propName, keyValue);
        }
        
        changeControl.getReadLock().lock();
        try {
            return internalLookup(propName, keyValue);
        }
        finally {
            changeControl.getReadLock().unlock();
        }
    }
    
    public Object _doAdd(String childProperty, Object rawChild, String childKey, int index) {
        if (changeControl == null) {
            return Utilities.internalAdd(this, childProperty, rawChild, childKey, index, null, XmlDynamicChange.EMPTY, new LinkedList>());
        }
        
        changeControl.getWriteLock().lock();
        try {
            XmlDynamicChange change = changeControl.startOrContinueChange(this);
            
            LinkedList> addedServices = new LinkedList>();
            Object retVal;
            boolean success = false;
            try {
                retVal = Utilities.internalAdd(this, childProperty, rawChild, childKey, index, changeControl, change, addedServices);
                success = true;
            }
            finally {
                changeControl.endOrDeferChange(success);
            }
            
            ServiceLocator locator = changeControl.getServiceLocator();
            for (ActiveDescriptor added : addedServices) {
                // Ensures that any defaulting listeners are run
                locator.getServiceHandle(added).getService();
            }
            
            return retVal;
        }
        finally {
            changeControl.getWriteLock().unlock();
        }
    }
    
    public Object _invokeCustomizedMethod(String methodName, Class[] params, Object[] values) {
        if (DEBUG_GETS_AND_SETS) {
            // Hidden behind static because of potential expensive toString costs
            Logger.getLogger().debug("XmlService invoking customized method " + methodName +
                    " with params " + Arrays.toString(params) + " adn values " + Arrays.toString(values));
        }
        Class tClass = getClass();
        Customizer customizer = tClass.getAnnotation(Customizer.class);
        if (customizer == null) {
            throw new RuntimeException("Method " + methodName + " was called on class " + tClass.getName() +
                    " with no customizer, failing");
        }
        
        Class cClassArray[] = customizer.value();
        String cNameArray[] = customizer.name();
        
        if (cNameArray.length > 0 && cClassArray.length != cNameArray.length) {
            throw new RuntimeException("The @Customizer annotation must have the value and name arrays be of equal size.  " +
              "The class array is of size " + cClassArray.length + " while the name array is of size " + cNameArray.length +
              " for class " + tClass.getName());
        }
        
        LinkedList errors = new LinkedList();
        for (int lcv = 0; lcv < cClassArray.length; lcv++) {
            Class cClass = cClassArray[lcv];
            String cName = (cNameArray.length == 0) ? null : cNameArray[lcv] ;
            
            Object cService = null;
            if (cName == null || "".equals(cName)) {
                cService = changeControl.getServiceLocator().getService(cClass);
            }
            else {
                cService = changeControl.getServiceLocator().getService(cClass, cName);
            }
        
            if (cService == null) {
                if (customizer.failWhenMethodNotFound()) {
                    errors.add(new RuntimeException("Method " + methodName + " was called on class " + tClass.getName() +
                        " but service " + cClass.getName() + " with name " + cName + " was not found"));
                }
            
                continue;
            }
        
            
            ModelImpl model = _getModel();
            Class topInterface = (model == null) ? null : model.getOriginalInterfaceAsClass() ;
            
            Method cMethod = Utilities.findSuitableCustomizerMethod(cClass, methodName, params, topInterface);
            if (cMethod == null) {
                if (customizer.failWhenMethodNotFound()) {
                    errors.add(new RuntimeException("No customizer method with name " + methodName + " was found on customizer " + cClass.getName() +
                            " with parameters " + Arrays.toString(params) + " for bean " + tClass.getName()));
                }
                
                continue;
            }
             
            boolean useAlt = false;
            if (cMethod.getParameterTypes().length == (params.length + 1)) useAlt = true;
            
            if (useAlt) {
                Object altValues[] = new Object[values.length + 1];
                altValues[0] = this;
                for (int lcv2 = 0; lcv2 < values.length; lcv2++) {
                    altValues[lcv2 + 1] = values[lcv2];
                }
            
                values = altValues;
            }
        
            try {
                return ReflectionHelper.invoke(cService, cMethod, values, false);
            }
            catch (RuntimeException re) {
                throw re;
            }
            catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
        
        if (errors.isEmpty()) {
            return null;
        }
        
        throw new MultiException(errors);
    }
    
    public int _invokeCustomizedMethodI(String methodName, Class[] params, Object[] values) {
        return ((Integer) _invokeCustomizedMethod(methodName, params, values)).intValue();
    }
    
    public long _invokeCustomizedMethodJ(String methodName, Class[] params, Object[] values) {
        return ((Long) _invokeCustomizedMethod(methodName, params, values)).longValue();
    }
    
    public boolean _invokeCustomizedMethodZ(String methodName, Class[] params, Object[] values) {
        return ((Boolean) _invokeCustomizedMethod(methodName, params, values)).booleanValue();
    }
    
    public byte _invokeCustomizedMethodB(String methodName, Class[] params, Object[] values) {
        return ((Byte) _invokeCustomizedMethod(methodName, params, values)).byteValue();
    }
    
    public char _invokeCustomizedMethodC(String methodName, Class[] params, Object[] values) {
        return ((Character) _invokeCustomizedMethod(methodName, params, values)).charValue();
    }
    
    public short _invokeCustomizedMethodS(String methodName, Class[] params, Object[] values) {
        return ((Short) _invokeCustomizedMethod(methodName, params, values)).shortValue();
    }
    
    public float _invokeCustomizedMethodF(String methodName, Class[] params, Object[] values) {
        return ((Float) _invokeCustomizedMethod(methodName, params, values)).floatValue();
    }
    
    public double _invokeCustomizedMethodD(String methodName, Class[] params, Object[] values) {
        return ((Double) _invokeCustomizedMethod(methodName, params, values)).doubleValue();
    }
    
    public Object _doRemove(String childProperty, String childKey, int index, Object child) {
        if (changeControl == null) {
            Object retVal = Utilities.internalRemove(this, childProperty, childKey, index, child, null, XmlDynamicChange.EMPTY);
            
            if (retVal != null) {
                keyedChildrenCache.remove(childProperty);
            }
            
            return Utilities.internalRemove(this, childProperty, childKey, index, child, null, XmlDynamicChange.EMPTY);
        }
        
        changeControl.getWriteLock().lock();
        try {
            XmlDynamicChange xmlDynamicChange = changeControl.startOrContinueChange(this);
            
            Object retVal;
            boolean success = false;
            try {
                retVal = Utilities.internalRemove(this, childProperty, childKey, index, child, changeControl, xmlDynamicChange);
                success = true;
            }
            finally {
                changeControl.endOrDeferChange(success);
            }
            
            if (retVal != null) {
                keyedChildrenCache.remove(childProperty);
            }
            
            return retVal;
        }
        finally {
            changeControl.getWriteLock().unlock();
        }
    }
    
    public boolean _doRemoveZ(String childProperty, String childKey, int index, Object child) {
        Object retVal = _doRemove(childProperty, childKey, index, child);
        return (retVal != null);
    }

    public boolean _hasProperty(String propName) {
        if (changeControl == null) {
            if (active) {
                synchronized (this) {
                    return beanLikeMap.containsKey(propName);
                }
            }
            
            return beanLikeMap.containsKey(propName);
        }
        
        changeControl.getReadLock().lock();
        try {
            return beanLikeMap.containsKey(propName);
        }
        finally {
            changeControl.getReadLock().unlock();
        }
    }

    /* (non-Javadoc)
     * @see org.glassfish.hk2.xml.api.XmlHk2ConfigurationBean#getBeanLikeMap()
     */
    @Override
    public Map _getBeanLikeMap() {
        if (changeControl == null) {
            if (active) {
                synchronized (this) {
                    return Collections.unmodifiableMap(new HashMap(beanLikeMap));
                }
            }
            return Collections.unmodifiableMap(new HashMap(beanLikeMap));
        }
        
        changeControl.getReadLock().lock();
        try {
            return Collections.unmodifiableMap(new HashMap(beanLikeMap));
        }
        finally {
            changeControl.getReadLock().unlock();
        }
    }
    
    /* (non-Javadoc)
     * @see org.glassfish.hk2.xml.api.XmlHk2ConfigurationBean#getParent()
     */
    @Override
    public Object _getParent() {
        return parent;
    }
    
    public void _setParent(Object parent) {
        this.parent = parent;
    }
    
    public void _setSelfXmlTag(String selfXmlTag) {
        this.selfXmlTag = selfXmlTag;
    }
    
    public String _getSelfXmlTag() {
        return selfXmlTag;
    }

    /* (non-Javadoc)
     * @see org.glassfish.hk2.xml.api.XmlHk2ConfigurationBean#_getXmlPath()
     */
    @Override
    public String _getXmlPath() {
        return xmlPath;
    }
    
    public void _setInstanceName(String name) {
        instanceName = name;
    }

    /* (non-Javadoc)
     * @see org.glassfish.hk2.xml.api.XmlHk2ConfigurationBean#_getInstanceName()
     */
    @Override
    public String _getInstanceName() {
        return instanceName;
    }
    
    public void _setKeyValue(String key) {
        keyValue = key;
    }
    
    /* (non-Javadoc)
     * @see org.glassfish.hk2.xml.api.XmlHk2ConfigurationBean#_getKeyPropertyName()
     */
    @Override
    public String _getKeyPropertyName() {
        return _getModel().getKeyProperty();
    }
    
    public void _setClassReflectionHelper(ClassReflectionHelper helper) {
        this.classReflectionHelper = helper;
    }
    
    @Override
    public String _getKeyValue() {
        return keyValue;
    }
    
    private static String calculateXmlPath(BaseHK2JAXBBean leaf) {
        LinkedList stack = new LinkedList();
        while (leaf != null) {
            stack.addFirst(leaf._getSelfXmlTag());
            
            leaf = (BaseHK2JAXBBean) leaf._getParent();
        }
        
        StringBuffer sb = new StringBuffer();
        for (String component : stack) {
            sb.append(XML_PATH_SEPARATOR + component);
        }
        
        return sb.toString();
    }
    
    public void _setDynamicChangeInfo(XmlRootHandleImpl root, DynamicChangeInfo change) {
        _setDynamicChangeInfo(root, change, true);
    }
    
    /**
     * Once this is set the dynamic change protocol is in effect,
     * and all paths can be calculated
     * 
     * @param change The change control object
     * @param doXmlPathCalculation if true then this should calculate the xml path at this time
     * (all the parent information must be correct).  If this is false it is assumed that this
     * is some sort of copy operation where the xmlPath has been pre-calculated and does not
     * need to be modified
     */
    public void _setDynamicChangeInfo(XmlRootHandleImpl root, DynamicChangeInfo change, boolean doXmlPathCalculation) {
        if (doXmlPathCalculation) {
            xmlPath = calculateXmlPath(this);
        }
        
        changeControl = change;
        this.root = root;
        if (changeControl != null) active = true;
    }
    
    /**
     * Once this has been set the bean is considered active, and
     * so defaulting can happen on the bean
     */
    public void _setActive() {
        active = true;
    }
    
    /**
     * Read lock must be held
     * 
     * @return The set of all children tags
     */
    public Set _getChildrenXmlTags() {
        HashSet retVal = new HashSet(_getModel().getKeyedChildren());
        retVal.addAll(_getModel().getUnKeyedChildren());
        
        return retVal;
    }
    
    /**
     * This copy method ONLY copies non-child and
     * non-parent and non-reference fields and so is
     * not a full copy.  The children and parent and
     * reference and lock information need to be filled
     * in later so as not to have links from one tree into
     * another.  The read lock of copyMe should be held
     * 
     * @param copyMe The non-null bean to copy FROM
     */
    public void _shallowCopyFrom(BaseHK2JAXBBean copyMe) {
        selfXmlTag = copyMe.selfXmlTag;
        instanceName = copyMe.instanceName;
        keyValue = copyMe.keyValue;
        xmlPath = copyMe.xmlPath;
        
        Map copyBeanLikeMap = copyMe._getBeanLikeMap();
        
        ModelImpl copyModel = copyMe._getModel();
        for (Map.Entry entrySet : copyBeanLikeMap.entrySet()) {
            String xmlTag = entrySet.getKey();
            
            if (copyModel.getKeyedChildren().contains(xmlTag) || copyMe._getModel().getUnKeyedChildren().contains(xmlTag)) continue;
            
            ChildDataModel cdm = copyModel.getNonChildProperties().get(xmlTag);
            if (cdm != null && cdm.isReference()) {
                continue;
            }
            
            beanLikeMap.put(entrySet.getKey(), entrySet.getValue());
        }
    }
    
    private PropertyChangeEvent changes[] = null;
    
    /**
     * Called with writeLock held
     * 
     * @param other
     * @param writeableDatabase
     */
    public void _merge(BaseHK2JAXBBean other, WriteableBeanDatabase writeableDatabase) {
        if (changes != null) throw new IllegalStateException("Bean " + this + " has a merge on-going");
        
        Map otherMap = other._getBeanLikeMap();
        
        WriteableType wt = null;
        if (writeableDatabase != null) {
            wt = writeableDatabase.getWriteableType(xmlPath);
        }
        
        changes = BeanReflectionHelper.getChangeEvents(classReflectionHelper,
                beanLikeMap, otherMap);
        
        Map modelChildren = _getModel().getChildrenProperties();
        for (Map.Entry modelChild : modelChildren.entrySet()) {
            String childXmlTag = modelChild.getKey();
            ParentedModel childPModel = modelChild.getValue();
            
            Object otherChildren = otherMap.get(childXmlTag);
            Object thisChildren = beanLikeMap.get(childXmlTag);
            if (otherChildren != null) {
                if (ChildType.DIRECT.equals(childPModel.getChildType())) {
                    BaseHK2JAXBBean otherChild = (BaseHK2JAXBBean) otherChildren;
                
                }
                else {
                    for (BaseHK2JAXBBean otherChild : (Iterable) otherChildren) {
                        
                    }
                }
            }
            else if (thisChildren != null) {
                // TODO: Remove
                if (ChildType.DIRECT.equals(childPModel.getChildType())) {
                    BaseHK2JAXBBean thisChild = (BaseHK2JAXBBean) thisChildren;
                
                }
                else {
                    for (BaseHK2JAXBBean thisChild : (Iterable) thisChildren) {
                        
                    }
                }
            }
        }
        
        if (wt != null) {
            wt.modifyInstance(instanceName, otherMap, changes);
        }
    }
    
    /**
     * Write lock for tree must be held
     * 
     * @param success If the transaction committed successfully
     */
    public void _endMerge(boolean success) {
        if (changes == null) throw new IllegalStateException("Bean " + this + " does not have a known merge");
        if (!success) {
            changes = null;
            return;
        }
        
        for (PropertyChangeEvent pce : changes) {
            String propName = pce.getPropertyName();
            Object newValue = pce.getNewValue();
            
            beanLikeMap.put(propName, newValue);
            
            // TODO: Children
        }
        
        changes = null;
    }
    
    /**
     * Called under write lock
     * 
     * @param propName The name of the property to change
     * @param propValue The new value of the property
     */
    public boolean changeInHub(String propName, Object propValue, WriteableBeanDatabase wbd) {
        if (GeneralUtilities.safeEquals(beanLikeMap.get(propName), propValue)) {
            // Calling set, but the value was not in fact changed
            return false;
        }
        
        WriteableType wt = wbd.getWriteableType(xmlPath);
        
        HashMap modified = new HashMap(beanLikeMap);
        modified.put(propName, propValue);
            
        wt.modifyInstance(instanceName, modified);
            
        return true;
    }
    
    /**
     * Write lock MUST be held
     * @param propName
     * @param propValue
     */
    private void changeInHubDirect(String propName, Object propValue) {
        if (changeControl == null) return;
        
        XmlDynamicChange xmlDynamicChange = changeControl.startOrContinueChange(this);
        
        boolean success = false;
        try {
            WriteableBeanDatabase wbd = xmlDynamicChange.getBeanDatabase();
            
            if (wbd == null) {
                success = true;
                return;
            }
            
            changeInHub(propName, propValue, wbd);
            
            success = true;
        }
        finally {
            changeControl.endOrDeferChange(success);
        }
        
    }
    
    /**
     * Gets the change control information for this bean
     * 
     * @return the change control information for this bean
     */
    public DynamicChangeInfo _getChangeControl() {
        return changeControl;
    }
    
    /**
     * Returns the reflection helper for this bean
     * 
     * @return The reflection helper for this bean
     */
    public ClassReflectionHelper _getClassReflectionHelper() {
        return classReflectionHelper;
    }
    
    public void _setSelfDescriptor(ActiveDescriptor selfDescriptor) {
        this.selfDescriptor = selfDescriptor;
    }
    
    @Override
    public ActiveDescriptor _getSelfDescriptor() {
        return selfDescriptor;
    }
    
    /**
     * Write lock must be held
     */
    public void __activateChange() {
        backupMap = null;
    }
    
    /**
     * Write lock must be held
     */
    public void __rollbackChange() {
        if (backupMap == null) return;
        
        beanLikeMap = backupMap;
        backupMap = null;
    }
    
    /**
     * Gets the root associated with this bean.  If this bean
     * has no associated root this will return null
     * 
     * @return The root of this bean, or null if this bean
     * is not associated with a root
     */
    public XmlRootHandle _getRoot() {
        return root;
    }
    
    @Override
    public String toString() {
        return "BaseHK2JAXBBean(XmlPath=" + xmlPath +
                ",instanceName=" + instanceName +
                ",keyValue=" + keyValue +
                ",model=" + _getModel() + 
                "," + System.identityHashCode(this) + ")";
    }
}