001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.jxpath.ri.model.beans; 019 020import org.apache.commons.jxpath.AbstractFactory; 021import org.apache.commons.jxpath.JXPathAbstractFactoryException; 022import org.apache.commons.jxpath.JXPathContext; 023import org.apache.commons.jxpath.JXPathIntrospector; 024import org.apache.commons.jxpath.ri.QName; 025import org.apache.commons.jxpath.ri.model.NodePointer; 026import org.apache.commons.jxpath.util.ValueUtils; 027 028/** 029 * A pointer allocated by a PropertyOwnerPointer to represent the value of a property of the parent object. 030 */ 031public abstract class PropertyPointer extends NodePointer { 032 033 private static final long serialVersionUID = 1L; 034 /** 035 * Marks a property as unspecified. 036 */ 037 public static final int UNSPECIFIED_PROPERTY = Integer.MIN_VALUE; 038 private static final Object UNINITIALIZED = new Object(); 039 040 /** Property index */ 041 protected int propertyIndex = UNSPECIFIED_PROPERTY; 042 043 /** Owning object */ 044 protected Object bean; 045 046 /** 047 * Supports {@link #getImmediateNode()}. 048 */ 049 private Object value = UNINITIALIZED; 050 051 /** 052 * Takes a JavaBean, a descriptor of a property of that object and an offset within that property (starting with 0). 053 * 054 * @param parent parent pointer 055 */ 056 public PropertyPointer(final NodePointer parent) { 057 super(parent); 058 } 059 060 @Override 061 public int compareChildNodePointers(final NodePointer pointer1, final NodePointer pointer2) { 062 return getValuePointer().compareChildNodePointers(pointer1, pointer2); 063 } 064 065 @Override 066 public NodePointer createChild(final JXPathContext context, final QName qName, final int index) { 067 final PropertyPointer prop = (PropertyPointer) clone(); 068 if (qName != null) { 069 prop.setPropertyName(qName.toString()); 070 } 071 prop.setIndex(index); 072 return prop.createPath(context); 073 } 074 075 @Override 076 public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) { 077 final PropertyPointer prop = (PropertyPointer) clone(); 078 if (qName != null) { 079 prop.setPropertyName(qName.toString()); 080 } 081 prop.setIndex(index); 082 return prop.createPath(context, value); 083 } 084 085 @Override 086 public NodePointer createPath(final JXPathContext context) { 087 if (getImmediateNode() == null) { 088 final AbstractFactory factory = getAbstractFactory(context); 089 final int inx = index == WHOLE_COLLECTION ? 0 : index; 090 final boolean success = factory.createObject(context, this, getBean(), getPropertyName(), inx); 091 if (!success) { 092 throw new JXPathAbstractFactoryException("Factory " + factory + " could not create an object for path: " + asPath()); 093 } 094 } 095 return this; 096 } 097 098 @Override 099 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}