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; 019 020import org.apache.commons.jxpath.AbstractFactory; 021import org.apache.commons.jxpath.JXPathAbstractFactoryException; 022import org.apache.commons.jxpath.JXPathContext; 023import org.apache.commons.jxpath.JXPathException; 024import org.apache.commons.jxpath.JXPathIntrospector; 025import org.apache.commons.jxpath.JXPathInvalidAccessException; 026import org.apache.commons.jxpath.Variables; 027import org.apache.commons.jxpath.ri.QName; 028import org.apache.commons.jxpath.ri.compiler.NodeTest; 029import org.apache.commons.jxpath.ri.model.beans.NullPointer; 030import org.apache.commons.jxpath.util.ValueUtils; 031 032/** 033 * Pointer to a context variable. 034 */ 035public class VariablePointer extends NodePointer { 036 037 private static final long serialVersionUID = -454731297397189293L; 038 039 /** 040 * Variables. 041 */ 042 private Variables variables; 043 044 /** 045 * Qualified name. 046 */ 047 private final QName qName; 048 049 /** 050 * Value pointer. 051 */ 052 private NodePointer valuePointer; 053 054 /** 055 * Implements {@link NodePointer#isActual()}. 056 */ 057 private boolean actual; 058 059 /** 060 * Constructs a new (non-actual) VariablePointer. 061 * 062 * @param qName variable name 063 */ 064 public VariablePointer(final QName qName) { 065 super(null); 066 this.qName = qName; 067 actual = false; 068 } 069 070 /** 071 * Constructs a new VariablePointer. 072 * 073 * @param variables Variables instance 074 * @param qName variable name 075 */ 076 public VariablePointer(final Variables variables, final QName qName) { 077 super(null); 078 this.variables = variables; 079 this.qName = qName; 080 actual = true; 081 } 082 083 @Override 084 public String asPath() { 085 final StringBuilder buffer = new StringBuilder(); 086 buffer.append('$'); 087 buffer.append(qName); 088 if (!actual) { 089 if (index != WHOLE_COLLECTION) { 090 buffer.append('[').append(index + 1).append(']'); 091 } 092 } else if (index != WHOLE_COLLECTION && (getNode() == null || isCollection())) { 093 buffer.append('[').append(index + 1).append(']'); 094 } 095 return buffer.toString(); 096 } 097 098 @Override 099 public NodeIterator attributeIterator(final QName qName) { 100 return getValuePointer().attributeIterator(qName); 101 } 102 103 @Override 104 public NodeIterator childIterator(final NodeTest test, final boolean reverse, final NodePointer startWith) { 105 return getValuePointer().childIterator(test, reverse, startWith); 106 } 107 108 @Override 109 public int compareChildNodePointers(final NodePointer pointer1, final NodePointer pointer2) { 110 return pointer1.getIndex() - pointer2.getIndex(); 111 } 112 113 @Override 114 public NodePointer createChild(final JXPathContext context, final QName qName, final int index) { 115 final Object collection = createCollection(context, index); 116 if (!isActual() || index != 0 && index != WHOLE_COLLECTION) { 117 final AbstractFactory factory = getAbstractFactory(context); 118 final boolean success = factory.createObject(context, this, collection, getName().toString(), index); 119 if (!success) { 120 throw new JXPathAbstractFactoryException("Factory could not create object path: " + asPath()); 121 } 122 final NodePointer cln = (NodePointer) clone(); 123 cln.setIndex(index); 124 return cln; 125 } 126 return this; 127 } 128 129 @Override 130 public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) { 131 final Object collection = createCollection(context, index); 132 ValueUtils.setValue(collection, index, value); 133 final NodePointer cl = (NodePointer) clone(); 134 cl.setIndex(index); 135 return cl; 136 } 137 138 /** 139 * Create a collection. 140 * 141 * @param context JXPathContext 142 * @param index collection index 143 * @return Object 144 */ 145 private Object createCollection(final JXPathContext context, int index) { 146 createPath(context); 147 Object collection = getBaseValue(); 148 if (collection == null) { 149 throw new JXPathAbstractFactoryException("Factory did not assign a collection to variable '" + qName + "' for path: " + asPath()); 150 } 151 if (index == WHOLE_COLLECTION) { 152 index = 0; 153 } else if (index < 0) { 154 throw new JXPathInvalidAccessException("Index is less than 1: " + asPath()); 155 } 156 if (index >= getLength()) { 157 collection = ValueUtils.expandCollection(collection, index + 1); 158 variables.declareVariable(qName.toString(), collection); 159 } 160 return collection; 161 } 162 163 @Override 164 public NodePointer createPath(final JXPathContext context) { 165 if (!actual) { 166 final AbstractFactory factory = getAbstractFactory(context); 167 if (!factory.declareVariable(context, qName.toString())) { 168 throw new JXPathAbstractFactoryException("Factory cannot define variable '" + qName + "' for path: " + asPath()); 169 } 170 findVariables(context); 171 // Assert: actual == true 172 } 173 return this; 174 } 175 176 @Override 177 public NodePointer createPath(final JXPathContext context, final Object value) { 178 if (actual) { 179 setValue(value); 180 return this; 181 } 182 final NodePointer ptr = createPath(context); 183 ptr.setValue(value); 184 return ptr; 185 } 186 187 @Override 188 public boolean equals(final Object object) { 189 if (object == this) { 190 return true; 191 } 192 if (!(object instanceof VariablePointer)) { 193 return false; 194 } 195 final VariablePointer other = (VariablePointer) object; 196 return variables == other.variables && qName.equals(other.qName) && index == other.index; 197 } 198 199 /** 200 * Assimilate the Variables instance associated with the specified context. 201 * 202 * @param context JXPathContext to search 203 */ 204 protected void findVariables(final JXPathContext context) { 205 valuePointer = null; 206 JXPathContext varCtx = context; 207 while (varCtx != null) { 208 variables = varCtx.getVariables(); 209 if (variables.isDeclaredVariable(qName.toString())) { 210 actual = true; 211 break; 212 } 213 varCtx = varCtx.getParentContext(); 214 variables = null; 215 } 216 } 217 218 @Override 219 public Object getBaseValue() { 220 if (!actual) { 221 throw new JXPathException("Undefined variable: " + qName); 222 } 223 return variables.getVariable(qName.toString()); 224 } 225 226 @Override 227 public Object getImmediateNode() { 228 final Object value = getBaseValue(); 229 return index == WHOLE_COLLECTION ? ValueUtils.getValue(value) : ValueUtils.getValue(value, index); 230 } 231 232 @Override 233 public NodePointer getImmediateValuePointer() { 234 if (valuePointer == null) { 235 Object value; 236 if (!actual) { 237 return new NullPointer(this, getName()) { 238 239 private static final long serialVersionUID = 1L; 240 241 @Override 242 public Object getImmediateNode() { 243 throw new JXPathException("Undefined variable: " + qName); 244 } 245 }; 246 } 247 value = getImmediateNode(); 248 valuePointer = newChildNodePointer(this, null, value); 249 } 250 return valuePointer; 251 } 252 253 @Override 254 public int getLength() { 255 if (actual) { 256 final Object value = getBaseValue(); 257 return value == null ? 1 : ValueUtils.getLength(value); 258 } 259 return 0; 260 } 261 262 @Override 263 public QName getName() { 264 return qName; 265 } 266 267 @Override 268 public int hashCode() { 269 return (actual ? System.identityHashCode(variables) : 0) + qName.hashCode() + index; 270 } 271 272 @Override 273 public boolean isActual() { 274 return actual; 275 } 276 277 @Override 278 public boolean isCollection() { 279 final Object value = getBaseValue(); 280 return value != null && ValueUtils.isCollection(value); 281 } 282 283 @Override 284 public boolean isContainer() { 285 return true; 286 } 287 288 @Override 289 public boolean isLeaf() { 290 final Object value = getNode(); 291 return value == null || JXPathIntrospector.getBeanInfo(value.getClass()).isAtomic(); 292 } 293 294 @Override 295 public NodeIterator namespaceIterator() { 296 return getValuePointer().namespaceIterator(); 297 } 298 299 @Override 300 public NodePointer namespacePointer(final String name) { 301 return getValuePointer().namespacePointer(name); 302 } 303 304 @Override 305 public void remove() { 306 if (actual) { 307 if (index == WHOLE_COLLECTION) { 308 variables.undeclareVariable(qName.toString()); 309 } else { 310 if (index < 0) { 311 throw new JXPathInvalidAccessException("Index is less than 1: " + asPath()); 312 } 313 Object collection = getBaseValue(); 314 if (collection != null && index < getLength()) { 315 collection = ValueUtils.remove(collection, index); 316 variables.declareVariable(qName.toString(), collection); 317 } 318 } 319 } 320 } 321 322 @Override 323 public void setIndex(final int index) { 324 super.setIndex(index); 325 valuePointer = null; 326 } 327 328 @Override 329 public void setValue(final Object value) { 330 if (!actual) { 331 throw new JXPathException("Cannot set undefined variable: " + qName); 332 } 333 valuePointer = null; 334 if (index != WHOLE_COLLECTION) { 335 final Object collection = getBaseValue(); 336 ValueUtils.setValue(collection, index, value); 337 } else { 338 variables.declareVariable(qName.toString(), value); 339 } 340 } 341 342 @Override 343 public boolean testNode(final NodeTest nodeTest) { 344 return getValuePointer().testNode(nodeTest); 345 } 346}