View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.model.beans;
19  
20  import org.apache.commons.jxpath.AbstractFactory;
21  import org.apache.commons.jxpath.JXPathAbstractFactoryException;
22  import org.apache.commons.jxpath.JXPathContext;
23  import org.apache.commons.jxpath.JXPathIntrospector;
24  import org.apache.commons.jxpath.ri.QName;
25  import org.apache.commons.jxpath.ri.model.NodePointer;
26  import org.apache.commons.jxpath.util.ValueUtils;
27  
28  /**
29   * A pointer allocated by a PropertyOwnerPointer to represent the value of a property of the parent object.
30   */
31  public abstract class PropertyPointer extends NodePointer {
32  
33      private static final long serialVersionUID = 1L;
34      /**
35       * Marks a property as unspecified.
36       */
37      public static final int UNSPECIFIED_PROPERTY = Integer.MIN_VALUE;
38      private static final Object UNINITIALIZED = new Object();
39  
40      /** Property index */
41      protected int propertyIndex = UNSPECIFIED_PROPERTY;
42  
43      /** Owning object */
44      protected Object bean;
45  
46      /**
47       * Supports {@link #getImmediateNode()}.
48       */
49      private Object value = UNINITIALIZED;
50  
51      /**
52       * Takes a JavaBean, a descriptor of a property of that object and an offset within that property (starting with 0).
53       *
54       * @param parent parent pointer
55       */
56      public PropertyPointer(final NodePointer parent) {
57          super(parent);
58      }
59  
60      @Override
61      public int compareChildNodePointers(final NodePointer pointer1, final NodePointer pointer2) {
62          return getValuePointer().compareChildNodePointers(pointer1, pointer2);
63      }
64  
65      @Override
66      public NodePointer createChild(final JXPathContext context, final QName qName, final int index) {
67          final PropertyPointer prop = (PropertyPointer) clone();
68          if (qName != null) {
69              prop.setPropertyName(qName.toString());
70          }
71          prop.setIndex(index);
72          return prop.createPath(context);
73      }
74  
75      @Override
76      public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) {
77          final PropertyPointer prop = (PropertyPointer) clone();
78          if (qName != null) {
79              prop.setPropertyName(qName.toString());
80          }
81          prop.setIndex(index);
82          return prop.createPath(context, value);
83      }
84  
85      @Override
86      public NodePointer createPath(final JXPathContext context) {
87          if (getImmediateNode() == null) {
88              final AbstractFactory factory = getAbstractFactory(context);
89              final int inx = index == WHOLE_COLLECTION ? 0 : index;
90              final boolean success = factory.createObject(context, this, getBean(), getPropertyName(), inx);
91              if (!success) {
92                  throw new JXPathAbstractFactoryException("Factory " + factory + " could not create an object for path: " + asPath());
93              }
94          }
95          return this;
96      }
97  
98      @Override
99      public NodePointer createPath(final JXPathContext context, final Object value) {
100         // If necessary, expand collection
101         if (index != WHOLE_COLLECTION && index >= getLength()) {
102             createPath(context);
103         }
104         setValue(value);
105         return this;
106     }
107 
108     @Override
109     public boolean equals(final Object object) {
110         if (object == this) {
111             return true;
112         }
113         if (!(object instanceof PropertyPointer)) {
114             return false;
115         }
116         final PropertyPointer other = (PropertyPointer) object;
117         if (parent != other.parent && (parent == null || !parent.equals(other.parent))) {
118             return false;
119         }
120         if (getPropertyIndex() != other.getPropertyIndex() || !getPropertyName().equals(other.getPropertyName())) {
121             return false;
122         }
123         final int iThis = index == WHOLE_COLLECTION ? 0 : index;
124         final int iOther = other.index == WHOLE_COLLECTION ? 0 : other.index;
125         return iThis == iOther;
126     }
127 
128     /**
129      * Gets the parent bean.
130      *
131      * @return Object
132      */
133     public Object getBean() {
134         if (bean == null) {
135             bean = getImmediateParentPointer().getNode();
136         }
137         return bean;
138     }
139 
140     @Override
141     public Object getImmediateNode() {
142         if (value == UNINITIALIZED) {
143             value = index == WHOLE_COLLECTION ? ValueUtils.getValue(getBaseValue()) : ValueUtils.getValue(getBaseValue(), index);
144         }
145         return value;
146     }
147 
148     /**
149      * Returns a NodePointer that can be used to access the currently selected property value.
150      *
151      * @return NodePointer
152      */
153     @Override
154     public NodePointer getImmediateValuePointer() {
155         return newChildNodePointer((NodePointer) clone(), getName(), getImmediateNode());
156     }
157 
158     /**
159      * If the property contains a collection, then the length of that collection, otherwise - 1.
160      *
161      * @return int length
162      */
163     @Override
164     public int getLength() {
165         final Object baseValue = getBaseValue();
166         return baseValue == null ? 1 : ValueUtils.getLength(baseValue);
167     }
168 
169     @Override
170     public QName getName() {
171         return new QName(null, getPropertyName());
172     }
173 
174     /**
175      * Count the number of properties represented.
176      *
177      * @return int
178      */
179     public abstract int getPropertyCount();
180 
181     /**
182      * Gets the property index.
183      *
184      * @return int index
185      */
186     public int getPropertyIndex() {
187         return propertyIndex;
188     }
189 
190     /**
191      * Gets the property name.
192      *
193      * @return String property name.
194      */
195     public abstract String getPropertyName();
196 
197     /**
198      * Gets the names of the included properties.
199      *
200      * @return String[]
201      */
202     public abstract String[] getPropertyNames();
203 
204     @Override
205     public int hashCode() {
206         return getImmediateParentPointer().hashCode() + propertyIndex + index;
207     }
208 
209     @Override
210     public boolean isActual() {
211         if (!isActualProperty()) {
212             return false;
213         }
214         return super.isActual();
215     }
216 
217     /**
218      * Tests whether this pointer references an actual property.
219      *
220      * @return true if actual
221      */
222     protected abstract boolean isActualProperty();
223 
224     @Override
225     public boolean isCollection() {
226         final Object value = getBaseValue();
227         return value != null && ValueUtils.isCollection(value);
228     }
229 
230     @Override
231     public boolean isLeaf() {
232         final Object value = getNode();
233         return value == null || JXPathIntrospector.getBeanInfo(value.getClass()).isAtomic();
234     }
235 
236     /**
237      * Sets the property index.
238      *
239      * @param index property index
240      */
241     public void setPropertyIndex(final int index) {
242         if (propertyIndex != index) {
243             propertyIndex = index;
244             setIndex(WHOLE_COLLECTION);
245         }
246     }
247 
248     /**
249      * Sets the property name.
250      *
251      * @param propertyName property name to set.
252      */
253     public abstract void setPropertyName(String propertyName);
254 }