1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation
  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. * http://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 org.apache.commons.jxpath.ri.model.dynamic;
  17. import java.util.Arrays;
  18. import java.util.Map;
  19. import org.apache.commons.jxpath.AbstractFactory;
  20. import org.apache.commons.jxpath.DynamicPropertyHandler;
  21. import org.apache.commons.jxpath.JXPathContext;
  22. import org.apache.commons.jxpath.JXPathException;
  23. import org.apache.commons.jxpath.ri.model.NodePointer;
  24. import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
  25. import org.apache.commons.jxpath.util.ValueUtils;
  26. /**
  27. * Pointer pointing to a property of an object with dynamic properties.
  28. *
  29. * @author Dmitri Plotnikov
  30. * @version $Revision: 1.8 $ $Date: 2004/04/04 22:06:36 $
  31. */
  32. public class DynamicPropertyPointer extends PropertyPointer {
  33. private DynamicPropertyHandler handler;
  34. private String name;
  35. private String[] names;
  36. private String requiredPropertyName;
  37. public DynamicPropertyPointer(
  38. NodePointer parent,
  39. DynamicPropertyHandler handler)
  40. {
  41. super(parent);
  42. this.handler = handler;
  43. }
  44. /**
  45. * This type of node is auxiliary.
  46. */
  47. public boolean isContainer() {
  48. return true;
  49. }
  50. /**
  51. * Number of the DP object's properties.
  52. */
  53. public int getPropertyCount() {
  54. return getPropertyNames().length;
  55. }
  56. /**
  57. * Names of all properties, sorted alphabetically
  58. */
  59. public String[] getPropertyNames() {
  60. if (names == null) {
  61. String allNames[] = handler.getPropertyNames(getBean());
  62. names = new String[allNames.length];
  63. for (int i = 0; i < names.length; i++) {
  64. names[i] = allNames[i];
  65. }
  66. Arrays.sort(names);
  67. if (requiredPropertyName != null) {
  68. int inx = Arrays.binarySearch(names, requiredPropertyName);
  69. if (inx < 0) {
  70. allNames = names;
  71. names = new String[allNames.length + 1];
  72. names[0] = requiredPropertyName;
  73. System.arraycopy(allNames, 0, names, 1, allNames.length);
  74. Arrays.sort(names);
  75. }
  76. }
  77. }
  78. return names;
  79. }
  80. /**
  81. * Returns the name of the currently selected property or "*"
  82. * if none has been selected.
  83. */
  84. public String getPropertyName() {
  85. if (name == null) {
  86. String names[] = getPropertyNames();
  87. if (propertyIndex >= 0 && propertyIndex < names.length) {
  88. name = names[propertyIndex];
  89. }
  90. else {
  91. name = "*";
  92. }
  93. }
  94. return name;
  95. }
  96. /**
  97. * Select a property by name. If the supplied name is
  98. * not one of the object's existing properties, it implicitly
  99. * adds this name to the object's property name list. It does not
  100. * set the property value though. In order to set the property
  101. * value, call setValue().
  102. */
  103. public void setPropertyName(String propertyName) {
  104. setPropertyIndex(UNSPECIFIED_PROPERTY);
  105. this.name = propertyName;
  106. requiredPropertyName = propertyName;
  107. if (names != null && Arrays.binarySearch(names, propertyName) < 0) {
  108. names = null;
  109. }
  110. }
  111. /**
  112. * Index of the currently selected property in the list of all
  113. * properties sorted alphabetically.
  114. */
  115. public int getPropertyIndex() {
  116. if (propertyIndex == UNSPECIFIED_PROPERTY) {
  117. String names[] = getPropertyNames();
  118. for (int i = 0; i < names.length; i++) {
  119. if (names[i].equals(name)) {
  120. setPropertyIndex(i);
  121. break;
  122. }
  123. }
  124. }
  125. return super.getPropertyIndex();
  126. }
  127. /**
  128. * Index a property by its index in the list of all
  129. * properties sorted alphabetically.
  130. */
  131. public void setPropertyIndex(int index) {
  132. if (propertyIndex != index) {
  133. super.setPropertyIndex(index);
  134. name = null;
  135. }
  136. }
  137. /**
  138. * Returns the value of the property, not an element of the collection
  139. * represented by the property, if any.
  140. */
  141. public Object getBaseValue() {
  142. return handler.getProperty(getBean(), getPropertyName());
  143. }
  144. /**
  145. * If index == WHOLE_COLLECTION, the value of the property, otherwise
  146. * the value of the index'th element of the collection represented by the
  147. * property. If the property is not a collection, index should be zero
  148. * and the value will be the property itself.
  149. */
  150. public Object getImmediateNode() {
  151. Object value;
  152. if (index == WHOLE_COLLECTION) {
  153. value = ValueUtils.getValue(handler.getProperty(
  154. getBean(),
  155. getPropertyName()));
  156. }
  157. else {
  158. value = ValueUtils.getValue(handler.getProperty(
  159. getBean(),
  160. getPropertyName()), index);
  161. }
  162. return value;
  163. }
  164. /**
  165. * A dynamic property is always considered actual - all keys are apparently
  166. * existing with possibly the value of null.
  167. */
  168. protected boolean isActualProperty() {
  169. return true;
  170. }
  171. /**
  172. * If index == WHOLE_COLLECTION, change the value of the property, otherwise
  173. * change the value of the index'th element of the collection
  174. * represented by the property.
  175. */
  176. public void setValue(Object value) {
  177. if (index == WHOLE_COLLECTION) {
  178. handler.setProperty(getBean(), getPropertyName(), value);
  179. }
  180. else {
  181. ValueUtils.setValue(
  182. handler.getProperty(getBean(), getPropertyName()),
  183. index,
  184. value);
  185. }
  186. }
  187. public NodePointer createPath(JXPathContext context) {
  188. // Ignore the name passed to us, use our own data
  189. Object collection = getBaseValue();
  190. if (collection == null) {
  191. AbstractFactory factory = getAbstractFactory(context);
  192. boolean success =
  193. factory.createObject(
  194. context,
  195. this,
  196. getBean(),
  197. getPropertyName(),
  198. 0);
  199. if (!success) {
  200. throw new JXPathException(
  201. "Factory could not create an object for path: " + asPath());
  202. }
  203. collection = getBaseValue();
  204. }
  205. if (index != WHOLE_COLLECTION) {
  206. if (index < 0) {
  207. throw new JXPathException("Index is less than 1: " + asPath());
  208. }
  209. if (index >= getLength()) {
  210. collection = ValueUtils.expandCollection(collection, index + 1);
  211. handler.setProperty(getBean(), getPropertyName(), collection);
  212. }
  213. }
  214. return this;
  215. }
  216. public NodePointer createPath(JXPathContext context, Object value) {
  217. if (index == WHOLE_COLLECTION) {
  218. handler.setProperty(getBean(), getPropertyName(), value);
  219. }
  220. else {
  221. createPath(context);
  222. ValueUtils.setValue(getBaseValue(), index, value);
  223. }
  224. return this;
  225. }
  226. public void remove() {
  227. if (index == WHOLE_COLLECTION) {
  228. removeKey();
  229. }
  230. else if (isCollection()) {
  231. Object collection = ValueUtils.remove(getBaseValue(), index);
  232. handler.setProperty(getBean(), getPropertyName(), collection);
  233. }
  234. else if (index == 0) {
  235. removeKey();
  236. }
  237. }
  238. private void removeKey() {
  239. Object bean = getBean();
  240. if (bean instanceof Map) {
  241. ((Map) bean).remove(getPropertyName());
  242. }
  243. else {
  244. handler.setProperty(bean, getPropertyName(), null);
  245. }
  246. }
  247. public String asPath() {
  248. StringBuffer buffer = new StringBuffer();
  249. buffer.append(getImmediateParentPointer().asPath());
  250. if (buffer.length() == 0) {
  251. buffer.append("/.");
  252. }
  253. else if (buffer.charAt(buffer.length() - 1) == '/') {
  254. buffer.append('.');
  255. }
  256. buffer.append("[@name='");
  257. buffer.append(escape(getPropertyName()));
  258. buffer.append("']");
  259. if (index != WHOLE_COLLECTION && isCollection()) {
  260. buffer.append('[').append(index + 1).append(']');
  261. }
  262. return buffer.toString();
  263. }
  264. private String escape(String string) {
  265. int index = string.indexOf('\'');
  266. while (index != -1) {
  267. string =
  268. string.substring(0, index)
  269. + "'"
  270. + string.substring(index + 1);
  271. index = string.indexOf('\'');
  272. }
  273. index = string.indexOf('\"');
  274. while (index != -1) {
  275. string =
  276. string.substring(0, index)
  277. + """
  278. + string.substring(index + 1);
  279. index = string.indexOf('\"');
  280. }
  281. return string;
  282. }
  283. private AbstractFactory getAbstractFactory(JXPathContext context) {
  284. AbstractFactory factory = context.getFactory();
  285. if (factory == null) {
  286. throw new JXPathException(
  287. "Factory is not set on the JXPathContext - cannot create path: "
  288. + asPath());
  289. }
  290. return factory;
  291. }
  292. }