|
What this is
This file is included in the DevDaily.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
Other links
The source code
/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.schema2beans;
import java.util.*;
import java.io.*;
import java.beans.*;
import org.w3c.dom.*;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
// To dynamically instanciate a wrapper object
import java.lang.reflect.*;
/**
* The DOMBinding class binds the bean properties of the bean graph to
* the nodes of the DOM graph. There is one DOMBinding object for
* one DOM node.
*
* A bean property never accesses a DOM node directly, but always through
* a DOMBinding object. In one bean graph, only one property will be linked
* to a DOMBinding object. However, if another graph is created for the same
* DOM graph, two bean properties will share the same DOM node using the
* DOMBinding object.
*
* As the DOMBinding object might be shared amoung different bean properties,
* it keeps a list of all the properties using it. This is how, for example,
* events can be fired to the beans of different graphs referencing a same
* node, when its value is changed.
*
* The beans of the bean graph lives there.
*/
public class DOMBinding {
// This integer uniquely identify this BeanProp in the graph
int id;
Node node;
// This is used by the BeanProp.setter(obj[]) to optimize its parsing
int pos;
// The same purpose but for ordering the DOM nodes
int posDOM;
private static final Class charArrayClass =
java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 0).getClass();
class BeanProperty {
boolean changed;
// BeanProp object containing the property
BeanProp beanProp;
// Null if the property is a String.
Object value;
// Last known real index into the BeanProp array before the removal
int lastIndex;
ArrayList attributes;
BeanProperty(BeanProp b) {
this.beanProp = b;
this.value = null;
this.changed = false;
this.lastIndex = -1;
this.attributes = null;
}
}
class CacheAttr {
String name;
String value;
CacheAttr(String name, String value) {
this.name = name;
this.value = value;
}
}
private BeanProperty prop;
public DOMBinding() {
this.id = DDFactory.getUniqueId();
}
public DOMBinding(Node node) {
this();
// DOM node this DOMBinding refers to
this.node = node;
}
void setNode(Node node) {
this.node = node;
}
void moveBefore(BeanProp prop, Node node) {
Node parent = prop.getParentNode();
parent.removeChild(this.node);
parent.insertBefore(this.node, node);
}
public int getId() {
return this.id;
}
String idToString() {
return Integer.toHexString(this.id);
}
Node getNode() {
return this.node;
}
/**
*/
void register(BeanProp prop, Object value) {
BeanProperty bp = new BeanProperty(prop);
if (Common.isBean(prop.type))
((BaseBean)value).setDomBinding(this);
this.prop = bp;
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG,
TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.BINDPROP,
"property " + prop.getDtdName() +
" bound to B(" + this.hashCode() + ")");
}
//
// Following is a little trick to deal with attribute that are not
// defined in the dtd. When we register this new element, we ask
// for all the attributes and add them dynamically, as transient,
// to the BeanProp list of attributes.
//
if (this.node != null) {
NamedNodeMap l = this.node.getAttributes();
for (int i=0; i 0) {
result += "+";
} else {
seconds = -1 * seconds;
result += "-";
}
int hours = seconds / 3600;
if (hours < 10)
result += "0";
result += hours + ":";
int minutes = (seconds / 60) % 60;
if (minutes < 10)
result += "0";
result += minutes;
return result;
}
/**
* The value is only stored locally. A later call to syncNodes()
* updates the DOM nodes with this local value.
*/
Object setValue(BeanProp prop, Object value) {
Object oldValue = null;
BeanProperty bp = this.getBeanProperty(prop);
if (bp != null) {
oldValue = bp.value;
// Use the value cache, otherwise get the Node tree value
if ((oldValue == null) && (this.node != null))
oldValue = this.getValue(prop);
if (Common.isBean(prop.type))
bp.value = value;
else
if (Common.isString(prop.type) && (value != null)) {
if (value instanceof org.netbeans.modules.schema2beans.QName) {
org.netbeans.modules.schema2beans.QName q =
(org.netbeans.modules.schema2beans.QName) value;
String prefix = q.getPrefix();
String declaredNS = "";
if ("".equals(prefix)) {
prefix = prop.getDtdName()+"_ns__";
q = new org.netbeans.modules.schema2beans.QName(q.getNamespaceURI(),
q.getLocalPart(),
prefix);
} else {
declaredNS = findNamespace(prefix);
}
if ("".equals(declaredNS)) {
// It's undeclared, so declare it.
((Element)node).setAttribute("xmlns:"+prefix,
q.getNamespaceURI());
prop.createTransientAttribute("xmlns:"+prefix);
}
} else {
Class cls = value.getClass();
String clsName = cls.getName();
if (clsName.equals("javax.xml.namespace.QName")) {
try {
Method prefixMethod = cls.getDeclaredMethod("getPrefix",
new Class[0]);
String prefix = (String) prefixMethod.invoke(value,
new Object[0]);
Method nsMethod = cls.getDeclaredMethod("getNamespaceURI",
new Class[0]);
String ns = (String) nsMethod.invoke(value,
new Object[0]);
String declaredNS = "";
if ("".equals(prefix)) {
Method localPartMethod = cls.getDeclaredMethod("getLocalPart",
new Class[0]);
String localPart = (String) localPartMethod.invoke(value,
new Object[0]);
Constructor c = cls.getDeclaredConstructor(new Class[] {String.class, String.class, String.class});
prefix = prop.getDtdName()+"_ns__";
value = c.newInstance(new Object[] {ns, localPart,
prefix});
} else {
declaredNS = findNamespace(prefix);
}
if ("".equals(declaredNS)) {
// It's undeclared, so declare it.
((Element)node).setAttribute("xmlns:"+prefix,
ns);
prop.createTransientAttribute("xmlns:"+prefix);
}
} catch (java.lang.NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (java.lang.IllegalAccessException e) {
throw new RuntimeException(e);
} catch (java.lang.InstantiationException e) {
throw new RuntimeException(e);
} catch (java.lang.reflect.InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
bp.value = this.getWrapperValue(value);
} else
bp.value = value;
}
return oldValue;
}
/** Workaround for TimeZone.getOffset which is not in JDK1.3 */
private static int timeZoneOffset (java.util.TimeZone tz, long date) {
if (tz.inDaylightTime(new Date(date))) {
return tz.getRawOffset() + (tz.useDaylightTime()? 3600000: 0);
}
return tz.getRawOffset();
}
/**
* Removes the reference to the property prop and delete the DOM Nodes.
*
*/
void remove(BeanProp prop) {
}
void removeProp(BeanProp prop) {
if (this.prop != null && this.prop.beanProp == prop)
this.prop = null;
}
// Remove a DOM node
void removeNode(BeanProp prop) {
if (this.node != null) {
Node parent = prop.getParentNode();
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.DELETENODE,
this.node.getNodeName() + " from " +
parent.getNodeName());
}
parent.removeChild(this.node);
this.node = null;
}
}
/**
* The BeanProp that changed the value calls the DOMBinding after
* the setValue in order to propagate the Changed event to all the
* BeanProp that share this same Node.
*/
void notifyBeansForChange(Object oldValue, Object newValue,
String attrName) {
if (this.prop != null) {
PropertyChangeEvent e = this.prop.beanProp.
prepareForChangeEvent(this, oldValue, newValue, attrName);
this.prop.beanProp.notifyInternal(e, true);
}
}
/**
* This method is called when time has come to update the DOM nodes
* with the local value of the DOMBinding.
* Typically, this happens when the user updates a final value of the
* bean tree.
*
* This method is called when a property has been set and the bean
* holding this property is already attached to a DOM Node. Only
* this condition allows the value to be flushed into the DOM tree.
*
*/
void syncNodes(BeanProp prop, BeanProp.Action a) {
BeanProperty bp = this.getBeanProperty(prop);
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.SYNCNODES,
a.toString() + " " + prop.getDtdName() +
(bp==null?" - unknown prop!":""));
}
if (bp == null)
return;
if(a.action == a.REMOVE) {
int i = prop.idToIndex(this.id);
if (i != -1)
bp.lastIndex = i;
PropertyChangeEvent e =
prop.prepareForChangeEvent(this, bp.value, null, null);
if (Common.isBean(prop.type)) {
// Recurse on all the properties of the bean
BaseBean bean = ((BaseBean)bp.value);
bean.syncNodes(a);
}
//System.out.println("this.prop="+this.prop+" this.prop.beanProp="+this.prop.beanProp+" this.prop.beanProp==prop "+(this.prop.beanProp==prop)+" this.prop.value="+this.prop.value+" bp.value="+bp.value);
if (this.prop != null && this.prop.beanProp==prop) {
// See IZ#19802
if (node != null &&
(prop.getType() & Common.MASK_TYPE) == Common.TYPE_STRING) {
// Since it's a String, the value is stored in the DOM
// graph, and not in our BeanProperty. Stash the contents
// of the DOM node into our BeanProperty,
// before we remove the node and lose the value.
bp.value = getDomValue(node);
}
//this.removeProp(prop);
}
this.removeNode(prop);
prop.notifyInternal(e, false);
}
else
if(a.action == a.ADD) {
if (Common.isBean(prop.type)) {
NodeFactory f = prop.getNodeFactory();
if (this.node != null) {
System.out.println("Removing from old graph.");
BeanProp.Action a2;
a2 = new BeanProp.Action(a.REMOVE);
syncNodes(this.prop.beanProp, a2);
/*
throw new IllegalStateException(Common.getMessage(
"DOMBindingAlreadyHasNode_msg",
node.toString()));
*/
}
Node parent = prop.getParentNode();
this.node = f.createElement(prop);
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.SYNCING,
"adding new child " +
this.node.getNodeName() +
" to node " + parent.getNodeName());
}
Node sibling = prop.getFollowingSibling(this);
parent.insertBefore(this.node, sibling);
// Recurse the syncNodes on all the properties of the bean
BaseBean bean = ((BaseBean)bp.value);
bean.setGraphManager(prop.bean.graphManager());
bean.syncNodes(a);
} else if (Common.isBoolean(prop.type)) {
boolean v = false;
if (bp.value != null)
v = ((Boolean)bp.value).booleanValue();
if (Common.shouldNotBeEmpty(prop.type) || node == null ||
(nodeToBoolean(prop)).booleanValue() != v) {
// Current node and expected value are not the same
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG,
TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.SYNCING,
(v?"adding new":"removing") +
" tag " +
prop.getDtdName());
}
Node parent = prop.getParentNode();
if (v || Common.shouldNotBeEmpty(prop.type)) {
NodeFactory f = prop.getNodeFactory();
if (node == null) {
node = f.createElement(prop);
Node sibling = prop.getFollowingSibling(this);
parent.insertBefore(this.node, sibling);
}
if (Common.shouldNotBeEmpty(prop.type)) {
CharacterData text =
(CharacterData) node.getFirstChild();
if (text == null) {
text = (CharacterData)f.createText();
node.appendChild(text);
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG,
TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.SYNCING,
"adding new text node " +
text.getNodeName() +
" to node " +
this.node.getNodeName());
}
}
text.setData(""+v);
}
} else if (node != null) {
parent.removeChild(this.node);
this.node = null;
}
}
else {
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG,
TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.SYNCING,
"keeping same boolean value");
}
}
} else {
NodeFactory f = prop.getNodeFactory();
if (this.node == null) {
Node parent = prop.getParentNode();
this.node = f.createElement(prop);
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG,
TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.SYNCING,
"adding new child " +
this.node.getNodeName() +
" to node " +
parent.getNodeName());
}
Node sibling = prop.getFollowingSibling(this);
parent.insertBefore(this.node, sibling);
}
CharacterData text =
(CharacterData)this.node.getFirstChild();
if (text == null) {
text = (CharacterData)f.createText();
this.node.appendChild(text);
if (DDLogFlags.debug) {
TraceLogger.put(TraceLogger.DEBUG,
TraceLogger.SVC_DD,
DDLogFlags.DBG_BLD, 1,
DDLogFlags.SYNCING,
"adding new text node " +
text.getNodeName() +
" to node " +
this.node.getNodeName());
}
}
text.setData(bp.value.toString());
}
// Add any attribute cached for this new node
if (this.node != null) {
if (bp.attributes != null) {
for (int i=0; i
|