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.JXPathInvalidAccessException; 024import org.apache.commons.jxpath.ri.QName; 025import org.apache.commons.jxpath.ri.model.NodePointer; 026 027/** 028 * Pointer to a null property. 029 */ 030public class NullPropertyPointer extends PropertyPointer { 031 032 private static final long serialVersionUID = 5296593071854982754L; 033 034 /** 035 * The property name. 036 */ 037 private String propertyName = "*"; 038 039 /** 040 * Whether this property is named. 041 */ 042 private boolean byNameAttribute; 043 044 /** 045 * Constructs a new NullPropertyPointer. 046 * 047 * @param parent pointer 048 */ 049 public NullPropertyPointer(final NodePointer parent) { 050 super(parent); 051 } 052 053 @Override 054 public String asPath() { 055 if (!byNameAttribute) { 056 return super.asPath(); 057 } 058 final StringBuilder buffer = new StringBuilder(); 059 buffer.append(getImmediateParentPointer().asPath()); 060 buffer.append("[@name='"); 061 buffer.append(escape(getPropertyName())); 062 buffer.append("']"); 063 if (index != WHOLE_COLLECTION) { 064 buffer.append('[').append(index + 1).append(']'); 065 } 066 return buffer.toString(); 067 } 068 069 /** 070 * Create a "bad factory" JXPathAbstractFactoryException for the specified AbstractFactory. 071 * 072 * @param factory AbstractFactory 073 * @return JXPathAbstractFactoryException 074 */ 075 private JXPathAbstractFactoryException createBadFactoryException(final AbstractFactory factory) { 076 return new JXPathAbstractFactoryException("Factory " + factory + " reported success creating object for path: " + asPath() 077 + " but object was null. Terminating to avoid stack recursion."); 078 } 079 080 @Override 081 public NodePointer createChild(final JXPathContext context, final QName qName, final int index) { 082 return createPath(context).createChild(context, qName, index); 083 } 084 085 @Override 086 public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) { 087 return createPath(context).createChild(context, qName, index, value); 088 } 089 090 @Override 091 public NodePointer createPath(final JXPathContext context) { 092 NodePointer newParent = parent.createPath(context); 093 if (isAttribute()) { 094 return newParent.createAttribute(context, getName()); 095 } 096 if (parent instanceof NullPointer && parent.equals(newParent)) { 097 throw createBadFactoryException(context.getFactory()); 098 } 099 // Consider these two use cases: 100 // 1. The parent pointer of NullPropertyPointer is 101 // a PropertyOwnerPointer other than NullPointer. When we call 102 // createPath on it, it most likely returns itself. We then 103 // take a PropertyPointer from it and get the PropertyPointer 104 // to expand the collection for the corresponding property. 105 // 106 // 2. The parent pointer of NullPropertyPointer is a NullPointer. 107 // When we call createPath, it may return a PropertyOwnerPointer 108 // or it may return anything else, like a DOMNodePointer. 109 // In the former case we need to do exactly what we did in use 110 // case 1. In the latter case, we simply request that the 111 // non-property pointer expand the collection by itself. 112 if (newParent instanceof PropertyOwnerPointer) { 113 final PropertyOwnerPointer pop = (PropertyOwnerPointer) newParent; 114 newParent = pop.getPropertyPointer(); 115 } 116 return newParent.createChild(context, getName(), getIndex()); 117 } 118 119 @Override 120 public NodePointer createPath(final JXPathContext context, final Object value) { 121 NodePointer newParent = parent.createPath(context); 122 if (isAttribute()) { 123 final NodePointer pointer = newParent.createAttribute(context, getName()); 124 pointer.setValue(value); 125 return pointer; 126 } 127 if (parent instanceof NullPointer && parent.equals(newParent)) { 128 throw createBadFactoryException(context.getFactory()); 129 } 130 if (newParent instanceof PropertyOwnerPointer) { 131 final PropertyOwnerPointer pop = (PropertyOwnerPointer) newParent; 132 newParent = pop.getPropertyPointer(); 133 } 134 return newParent.createChild(context, getName(), index, value); 135 } 136 137 @Override 138 public Object getBaseValue() { 139 return null; 140 } 141 142 @Override 143 public Object getImmediateNode() { 144 return null; 145 } 146 147 @Override 148 public int getLength() { 149 return 0; 150 } 151 152 @Override 153 public QName getName() { 154 return new QName(propertyName); 155 } 156 157 @Override 158 public int getPropertyCount() { 159 return 0; 160 } 161 162 @Override 163 public String getPropertyName() { 164 return propertyName; 165 } 166 167 @Override 168 public String[] getPropertyNames() { 169 return new String[0]; 170 } 171 172 @Override 173 public NodePointer getValuePointer() { 174 return new NullPointer(this, new QName(getPropertyName())); 175 } 176 177 @Override 178 public boolean isActual() { 179 return false; 180 } 181 182 @Override 183 protected boolean isActualProperty() { 184 return false; 185 } 186 187 @Override 188 public boolean isCollection() { 189 return getIndex() != WHOLE_COLLECTION; 190 } 191 192 @Override 193 public boolean isContainer() { 194 return true; 195 } 196 197 @Override 198 public boolean isLeaf() { 199 return true; 200 } 201 202 /** 203 * Sets the name attribute. 204 * 205 * @param attributeValue value to set 206 */ 207 public void setNameAttributeValue(final String attributeValue) { 208 this.propertyName = attributeValue; 209 byNameAttribute = true; 210 } 211 212 @Override 213 public void setPropertyIndex(final int index) { 214 } 215 216 @Override 217 public void setPropertyName(final String propertyName) { 218 this.propertyName = propertyName; 219 } 220 221 @Override 222 public void setValue(final Object value) { 223 if (parent == null || parent.isContainer()) { 224 throw new JXPathInvalidAccessException("Cannot set property " + asPath() + ", the target object is null"); 225 } 226 if (!(parent instanceof PropertyOwnerPointer) || !((PropertyOwnerPointer) parent).isDynamicPropertyDeclarationSupported()) { 227 throw new JXPathInvalidAccessException("Cannot set property " + asPath() + ", path does not match a changeable location"); 228 } 229 // If the parent property owner can create 230 // a property automatically - let it do so 231 final PropertyPointer propertyPointer = ((PropertyOwnerPointer) parent).getPropertyPointer(); 232 propertyPointer.setPropertyName(propertyName); 233 propertyPointer.setValue(value); 234 } 235}