/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jxpath.ri.model.dynamic;
import java.util.Arrays;
import java.util.Map;
import org.apache.commons.jxpath.AbstractFactory;
import org.apache.commons.jxpath.DynamicPropertyHandler;
import org.apache.commons.jxpath.JXPathAbstractFactoryException;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathInvalidAccessException;
import org.apache.commons.jxpath.ri.model.NodePointer;
import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
import org.apache.commons.jxpath.util.ValueUtils;
/**
* Pointer pointing to a property of an object with dynamic properties.
*
* @author Dmitri Plotnikov
* @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $
*/
public class DynamicPropertyPointer extends PropertyPointer {
private static final long serialVersionUID = -5720585681149150822L;
private DynamicPropertyHandler handler;
private String name;
private String[] names;
private String requiredPropertyName;
/**
* Create a new DynamicPropertyPointer.
* @param parent pointer
* @param handler DynamicPropertyHandler
*/
public DynamicPropertyPointer(NodePointer parent,
DynamicPropertyHandler handler) {
super(parent);
this.handler = handler;
}
/**
* This type of node is auxiliary.
* @return true
*/
public boolean isContainer() {
return true;
}
/**
* Number of the DP object's properties.
* @return int
*/
public int getPropertyCount() {
return getPropertyNames().length;
}
/**
* Names of all properties, sorted alphabetically.
* @return String[]
*/
public String[] getPropertyNames() {
if (names == null) {
String[] allNames = handler.getPropertyNames(getBean());
names = new String[allNames.length];
for (int i = 0; i < names.length; i++) {
names[i] = allNames[i];
}
Arrays.sort(names);
if (requiredPropertyName != null) {
int inx = Arrays.binarySearch(names, requiredPropertyName);
if (inx < 0) {
allNames = names;
names = new String[allNames.length + 1];
names[0] = requiredPropertyName;
System.arraycopy(allNames, 0, names, 1, allNames.length);
Arrays.sort(names);
}
}
}
return names;
}
/**
* Returns the name of the currently selected property or "*"
* if none has been selected.
* @return String
*/
public String getPropertyName() {
if (name == null) {
String[] names = getPropertyNames();
name = propertyIndex >= 0 && propertyIndex < names.length ? names[propertyIndex] : "*";
}
return name;
}
/**
* Select a property by name. If the supplied name is
* not one of the object's existing properties, it implicitly
* adds this name to the object's property name list. It does not
* set the property value though. In order to set the property
* value, call setValue().
* @param propertyName to set
*/
public void setPropertyName(String propertyName) {
setPropertyIndex(UNSPECIFIED_PROPERTY);
this.name = propertyName;
requiredPropertyName = propertyName;
if (names != null && Arrays.binarySearch(names, propertyName) < 0) {
names = null;
}
}
/**
* Index of the currently selected property in the list of all
* properties sorted alphabetically.
* @return int
*/
public int getPropertyIndex() {
if (propertyIndex == UNSPECIFIED_PROPERTY) {
String[] names = getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(name)) {
setPropertyIndex(i);
break;
}
}
}
return super.getPropertyIndex();
}
/**
* Index a property by its index in the list of all
* properties sorted alphabetically.
* @param index to set
*/
public void setPropertyIndex(int index) {
if (propertyIndex != index) {
super.setPropertyIndex(index);
name = null;
}
}
/**
* Returns the value of the property, not an element of the collection
* represented by the property, if any.
* @return Object
*/
public Object getBaseValue() {
return handler.getProperty(getBean(), getPropertyName());
}
/**
* If index == WHOLE_COLLECTION, the value of the property, otherwise
* the value of the index'th element of the collection represented by the
* property. If the property is not a collection, index should be zero
* and the value will be the property itself.
* @return Object
*/
public Object getImmediateNode() {
Object value;
if (index == WHOLE_COLLECTION) {
value = ValueUtils.getValue(handler.getProperty(
getBean(),
getPropertyName()));
}
else {
value = ValueUtils.getValue(handler.getProperty(
getBean(),
getPropertyName()), index);
}
return value;
}
/**
* A dynamic property is always considered actual - all keys are apparently
* existing with possibly the value of null.
* @return boolean
*/
protected boolean isActualProperty() {
return true;
}
/**
* If index == WHOLE_COLLECTION, change the value of the property, otherwise
* change the value of the index'th element of the collection
* represented by the property.
* @param value to set
*/
public void setValue(Object value) {
if (index == WHOLE_COLLECTION) {
handler.setProperty(getBean(), getPropertyName(), value);
}
else {
ValueUtils.setValue(
handler.getProperty(getBean(), getPropertyName()),
index,
value);
}
}
public NodePointer createPath(JXPathContext context) {
// Ignore the name passed to us, use our own data
Object collection = getBaseValue();
if (collection == null) {
AbstractFactory factory = getAbstractFactory(context);
boolean success =
factory.createObject(
context,
this,
getBean(),
getPropertyName(),
0);
if (!success) {
throw new JXPathAbstractFactoryException(
"Factory could not create an object for path: " + asPath());
}
collection = getBaseValue();
}
if (index != WHOLE_COLLECTION) {
if (index < 0) {
throw new JXPathInvalidAccessException("Index is less than 1: "
+ asPath());
}
if (index >= getLength()) {
collection = ValueUtils.expandCollection(collection, index + 1);
handler.setProperty(getBean(), getPropertyName(), collection);
}
}
return this;
}
public NodePointer createPath(JXPathContext context, Object value) {
if (index == WHOLE_COLLECTION) {
handler.setProperty(getBean(), getPropertyName(), value);
}
else {
createPath(context);
ValueUtils.setValue(getBaseValue(), index, value);
}
return this;
}
public void remove() {
if (index == WHOLE_COLLECTION) {
removeKey();
}
else if (isCollection()) {
Object collection = ValueUtils.remove(getBaseValue(), index);
handler.setProperty(getBean(), getPropertyName(), collection);
}
else if (index == 0) {
removeKey();
}
}
/**
* Remove the current property.
*/
private void removeKey() {
Object bean = getBean();
if (bean instanceof Map) {
((Map) bean).remove(getPropertyName());
}
else {
handler.setProperty(bean, getPropertyName(), null);
}
}
public String asPath() {
StringBuffer buffer = new StringBuffer();
buffer.append(getImmediateParentPointer().asPath());
if (buffer.length() == 0) {
buffer.append("/.");
}
else if (buffer.charAt(buffer.length() - 1) == '/') {
buffer.append('.');
}
buffer.append("[@name='");
buffer.append(escape(getPropertyName()));
buffer.append("']");
if (index != WHOLE_COLLECTION && isCollection()) {
buffer.append('[').append(index + 1).append(']');
}
return buffer.toString();
}
}
|