View Javadoc
1   /*
2    * Copyright 2004-2025 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.ibatis.common.beans;
17  
18  import com.ibatis.common.resources.Resources;
19  
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.StringTokenizer;
27  
28  import org.w3c.dom.CharacterData;
29  import org.w3c.dom.Document;
30  import org.w3c.dom.Element;
31  import org.w3c.dom.NamedNodeMap;
32  import org.w3c.dom.Node;
33  import org.w3c.dom.NodeList;
34  import org.w3c.dom.Text;
35  
36  /**
37   * A Probe implementation for working with DOM objects.
38   */
39  public class DomProbe extends BaseProbe {
40  
41    @Override
42    public String[] getReadablePropertyNames(Object object) {
43      List props = new ArrayList<>();
44      Element e = resolveElement(object);
45      NodeList nodes = e.getChildNodes();
46      for (int i = 0; i < nodes.getLength(); i++) {
47        props.add(nodes.item(i).getNodeName());
48      }
49      return (String[]) props.toArray(new String[props.size()]);
50    }
51  
52    @Override
53    public String[] getWriteablePropertyNames(Object object) {
54      List props = new ArrayList<>();
55      Element e = resolveElement(object);
56      NodeList nodes = e.getChildNodes();
57      for (int i = 0; i < nodes.getLength(); i++) {
58        props.add(nodes.item(i).getNodeName());
59      }
60      return (String[]) props.toArray(new String[props.size()]);
61    }
62  
63    @Override
64    public Class getPropertyTypeForSetter(Object object, String name) {
65      Element e = findNestedNodeByName(resolveElement(object), name, false);
66      // todo alias types, don't use exceptions like this
67      try {
68        return Resources.classForName(e.getAttribute("type"));
69      } catch (ClassNotFoundException e1) {
70        return Object.class;
71      }
72    }
73  
74    @Override
75    public Class getPropertyTypeForGetter(Object object, String name) {
76      Element e = findNestedNodeByName(resolveElement(object), name, false);
77      // todo alias types, don't use exceptions like this
78      try {
79        return Resources.classForName(e.getAttribute("type"));
80      } catch (ClassNotFoundException e1) {
81        return Object.class;
82      }
83    }
84  
85    @Override
86    public boolean hasWritableProperty(Object object, String propertyName) {
87      return findNestedNodeByName(resolveElement(object), propertyName, false) != null;
88    }
89  
90    @Override
91    public boolean hasReadableProperty(Object object, String propertyName) {
92      return findNestedNodeByName(resolveElement(object), propertyName, false) != null;
93    }
94  
95    @Override
96    public Object getObject(Object object, String name) {
97      Object value = null;
98      Element element = findNestedNodeByName(resolveElement(object), name, false);
99      if (element != null) {
100       value = getElementValue(element);
101     }
102     return value;
103   }
104 
105   @Override
106   public void setObject(Object object, String name, Object value) {
107     Element element = findNestedNodeByName(resolveElement(object), name, true);
108     if (element != null) {
109       setElementValue(element, value);
110     }
111   }
112 
113   @Override
114   protected void setProperty(Object object, String property, Object value) {
115     Element element = findNodeByName(resolveElement(object), property, 0, true);
116     if (element != null) {
117       setElementValue(element, value);
118     }
119   }
120 
121   @Override
122   protected Object getProperty(Object object, String property) {
123     Object value = null;
124     Element element = findNodeByName(resolveElement(object), property, 0, false);
125     if (element != null) {
126       value = getElementValue(element);
127     }
128     return value;
129   }
130 
131   /**
132    * Resolve element.
133    *
134    * @param object
135    *          the object
136    *
137    * @return the element
138    */
139   private Element resolveElement(Object object) {
140     Element element = null;
141     if (object instanceof Document) {
142       element = (Element) ((Document) object).getLastChild();
143     } else if (object instanceof Element) {
144       element = (Element) object;
145     } else {
146       throw new ProbeException("An unknown object type was passed to DomProbe.  Must be a Document.");
147     }
148     return element;
149   }
150 
151   /**
152    * Sets the element value.
153    *
154    * @param element
155    *          the element
156    * @param value
157    *          the value
158    */
159   private void setElementValue(Element element, Object value) {
160     CharacterData data = null;
161 
162     Element prop = element;
163 
164     if (value instanceof Collection) {
165       Iterator items = ((Collection) value).iterator();
166       while (items.hasNext()) {
167         Document valdoc = (Document) items.next();
168         NodeList list = valdoc.getChildNodes();
169         for (int i = 0; i < list.getLength(); i++) {
170           Node newNode = element.getOwnerDocument().importNode(list.item(i), true);
171           element.appendChild(newNode);
172         }
173       }
174     } else if (value instanceof Document) {
175       Document valdoc = (Document) value;
176       Node lastChild = valdoc.getLastChild();
177       NodeList list = lastChild.getChildNodes();
178       for (int i = 0; i < list.getLength(); i++) {
179         Node newNode = element.getOwnerDocument().importNode(list.item(i), true);
180         element.appendChild(newNode);
181       }
182     } else if (value instanceof Element) {
183       Node newNode = element.getOwnerDocument().importNode((Element) value, true);
184       element.appendChild(newNode);
185     } else {
186       // Find text child element
187       NodeList texts = prop.getChildNodes();
188       if (texts.getLength() == 1) {
189         Node child = texts.item(0);
190         if (child instanceof CharacterData) {
191           // Use existing text.
192           data = (CharacterData) child;
193         } else {
194           // Remove non-text, add text.
195           prop.removeChild(child);
196           Text text = prop.getOwnerDocument().createTextNode(String.valueOf(value));
197           prop.appendChild(text);
198           data = text;
199         }
200       } else if (texts.getLength() > 1) {
201         // Remove all, add text.
202         for (int i = texts.getLength() - 1; i >= 0; i--) {
203           prop.removeChild(texts.item(i));
204         }
205         Text text = prop.getOwnerDocument().createTextNode(String.valueOf(value));
206         prop.appendChild(text);
207         data = text;
208       } else {
209         // Add text.
210         Text text = prop.getOwnerDocument().createTextNode(String.valueOf(value));
211         prop.appendChild(text);
212         data = text;
213       }
214       data.setData(String.valueOf(value));
215     }
216 
217     // Set type attribute
218     // prop.setAttribute("type", value == null ? "null" : value.getClass().getName());
219 
220   }
221 
222   /**
223    * Gets the element value.
224    *
225    * @param element
226    *          the element
227    *
228    * @return the element value
229    */
230   private Object getElementValue(Element element) {
231     StringBuilder value = null;
232 
233     Element prop = element;
234 
235     if (prop != null) {
236       // Find text child elements
237       NodeList texts = prop.getChildNodes();
238       if (texts.getLength() > 0) {
239         value = new StringBuilder();
240         for (int i = 0; i < texts.getLength(); i++) {
241           Node text = texts.item(i);
242           if (text instanceof CharacterData) {
243             value.append(((CharacterData) text).getData());
244           }
245         }
246       }
247     }
248 
249     // convert to proper type
250     // value = convert(value.toString());
251 
252     if (value == null) {
253       return null;
254     }
255     return value.toString();
256   }
257 
258   /**
259    * Find nested node by name.
260    *
261    * @param element
262    *          the element
263    * @param name
264    *          the name
265    * @param create
266    *          the create
267    *
268    * @return the element
269    */
270   private Element findNestedNodeByName(Element element, String name, boolean create) {
271     Element child = element;
272 
273     StringTokenizer parser = new StringTokenizer(name, ".", false);
274     while (parser.hasMoreTokens()) {
275       String childName = parser.nextToken();
276       if (childName.indexOf('[') > -1) {
277         String propName = childName.substring(0, childName.indexOf('['));
278         int i = Integer.parseInt(childName.substring(childName.indexOf('[') + 1, childName.indexOf(']')));
279         child = findNodeByName(child, propName, i, create);
280       } else {
281         child = findNodeByName(child, childName, 0, create);
282       }
283       if (child == null) {
284         break;
285       }
286     }
287 
288     return child;
289   }
290 
291   /**
292    * Find node by name.
293    *
294    * @param element
295    *          the element
296    * @param name
297    *          the name
298    * @param index
299    *          the index
300    * @param create
301    *          the create
302    *
303    * @return the element
304    */
305   private Element findNodeByName(Element element, String name, int index, boolean create) {
306     Element prop = null;
307 
308     // Find named property element
309     NodeList propNodes = element.getElementsByTagName(name);
310     if (propNodes.getLength() > index) {
311       prop = (Element) propNodes.item(index);
312     } else if (create) {
313       for (int i = 0; i < index + 1; i++) {
314         prop = element.getOwnerDocument().createElement(name);
315         element.appendChild(prop);
316       }
317     }
318     return prop;
319   }
320 
321   /**
322    * Converts a DOM node to a complete xml string.
323    *
324    * @param node
325    *          - the node to process
326    * @param indent
327    *          - how to indent the children of the node
328    *
329    * @return The node as a String
330    */
331   public static String nodeToString(Node node, String indent) {
332     StringWriter stringWriter = new StringWriter();
333     PrintWriter printWriter = new PrintWriter(stringWriter);
334 
335     switch (node.getNodeType()) {
336 
337       case Node.DOCUMENT_NODE:
338         printWriter.println("<xml version=\"1.0\">\n");
339         // recurse on each child
340         NodeList nodes = node.getChildNodes();
341         if (nodes != null) {
342           for (int i = 0; i < nodes.getLength(); i++) {
343             printWriter.print(nodeToString(nodes.item(i), ""));
344           }
345         }
346         break;
347 
348       case Node.ELEMENT_NODE:
349         String name = node.getNodeName();
350         printWriter.print(indent + "<" + name);
351         NamedNodeMap attributes = node.getAttributes();
352         for (int i = 0; i < attributes.getLength(); i++) {
353           Node current = attributes.item(i);
354           printWriter.print(" " + current.getNodeName() + "=\"" + current.getNodeValue() + "\"");
355         }
356         printWriter.print(">");
357 
358         // recurse on each child
359         NodeList children = node.getChildNodes();
360         if (children != null) {
361           for (int i = 0; i < children.getLength(); i++) {
362             printWriter.print(nodeToString(children.item(i), indent + indent));
363           }
364         }
365 
366         printWriter.print("</" + name + ">");
367         break;
368 
369       case Node.TEXT_NODE:
370         printWriter.print(node.getNodeValue());
371         break;
372     }
373 
374     printWriter.flush();
375     String result = stringWriter.getBuffer().toString();
376     printWriter.close();
377 
378     return result;
379   }
380 
381 }