| /******************************************************************************* |
| * Copyright (c) 2006 Oracle Corporation. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Cameron Bateman/Oracle - initial API and implementation |
| * |
| ********************************************************************************/ |
| |
| package org.eclipse.jst.jsf.designtime.el; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.emf.common.util.BasicEList; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.jdt.core.Signature; |
| import org.eclipse.jst.jsf.common.internal.types.StringLiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.TypeConstants; |
| import org.eclipse.jst.jsf.common.internal.types.ValueType; |
| import org.eclipse.jst.jsf.context.symbol.IBoundedTypeDescriptor; |
| import org.eclipse.jst.jsf.context.symbol.IObjectSymbol; |
| import org.eclipse.jst.jsf.context.symbol.ISymbol; |
| import org.eclipse.jst.jsf.context.symbol.ITypeDescriptor; |
| |
| /** |
| * A design time proxy for the runtime PropertyResolver. This is used to |
| * resolve all but the first element of a var.prop.prop2 type of sub-expression in |
| * a JSF EL expression. @see DefaultDTVariableResolver for how to resolve 'var' at |
| * designtime |
| * |
| * Clients may implement |
| * |
| * @author cbateman |
| */ |
| public class DefaultDTPropertyResolver extends AbstractDTPropertyResolver |
| { |
| /** |
| * Returns a symbol encapsulating the property on base with the name |
| * properyId |
| * |
| * @param base |
| * @param propertyId |
| * @return the symbol for the named propertyId or null if not found |
| */ |
| public ISymbol getProperty(ISymbol base, Object propertyId) |
| { |
| ITypeDescriptor typeDesc = null; |
| |
| Object[] factoredProperties = new Object[] {propertyId}; |
| |
| // check for expected interface types per JSP.2.3.4 |
| if (base instanceof IObjectSymbol) |
| { |
| final IObjectSymbol objSymbol = (IObjectSymbol) base; |
| typeDesc = objSymbol.getTypeDescriptor(); |
| |
| // although not stated explicitly stated by the spec, in practice (based on Sun RI), |
| // a list cannot have non-numeric indexed properties |
| // note: due to remove(Object) having different return types |
| // an object can't be both a List and a Map! So we can consider |
| // a List instanceof out of order |
| if (objSymbol.supportsCoercion(TypeConstants.TYPE_LIST)) |
| { |
| typeDesc = null; |
| } |
| // per JSP.2.3.4, if instance of map (unconstrained in our terminology) |
| else if (objSymbol.supportsCoercion(TypeConstants.TYPE_MAP)) |
| { |
| EList<ValueType> args = new BasicEList<ValueType>(); |
| args.add(new StringLiteralType(propertyId.toString())); |
| |
| ISymbol prop = objSymbol.call("get", args, propertyId.toString()); //$NON-NLS-1$ |
| |
| if (prop != null) |
| { |
| return prop; |
| } |
| |
| typeDesc = objSymbol.coerce(TypeConstants.TYPE_MAP); |
| |
| // handle string keys into maps that contain dots. Because type descriptor |
| // handle dotted property ids (i.e. 'x.y.z') as separate properties with |
| // intermediate parts, we need to handle this specially. |
| if (propertyId instanceof String && ((String)propertyId).indexOf('.')>-1) |
| { |
| factoredProperties = factorKey(propertyId); |
| } |
| } |
| |
| // check unconstrained type |
| if (typeDesc instanceof IBoundedTypeDescriptor) |
| { |
| // TODO: propertyId may need to change when supporting |
| // template types |
| if (((IBoundedTypeDescriptor)typeDesc).isUnboundedForType(TypeConstants.TYPE_JAVAOBJECT)) |
| { |
| // the most we know is that it could be an Object |
| return ((IBoundedTypeDescriptor)typeDesc).getUnboundedProperty(propertyId, TypeConstants.TYPE_JAVAOBJECT); |
| } |
| } |
| } |
| |
| int i = 0; |
| ISymbol matchedSymbol; |
| |
| do |
| { |
| matchedSymbol = null; // always reset so if the for completes without setting, the |
| // while ends |
| SEARCH_SEGMENT: for (final Iterator it = getIterator(typeDesc); it.hasNext();) |
| { |
| final ISymbol element = (ISymbol) it.next(); |
| |
| if (element.getName().equals(factoredProperties[i]) |
| && element instanceof IObjectSymbol) |
| { |
| matchedSymbol = element; |
| typeDesc = ((IObjectSymbol)matchedSymbol).getTypeDescriptor(); |
| break SEARCH_SEGMENT; |
| } |
| } |
| } while(++i < factoredProperties.length && matchedSymbol != null); |
| |
| // may be null if none matched |
| return matchedSymbol; |
| } |
| |
| /** |
| * @param base |
| * @return all properties of base |
| */ |
| public ISymbol[] getAllProperties(ISymbol base) |
| { |
| // if nothing found, return an empty array |
| List symbolsList = Collections.EMPTY_LIST; |
| |
| if (base instanceof IObjectSymbol) |
| { |
| ITypeDescriptor typeDesc = null; |
| |
| // per JSP.2.3.4, if instance of map (unconstrained in our terminology) |
| if (((IObjectSymbol)base).supportsCoercion(TypeConstants.TYPE_MAP)) |
| { |
| typeDesc = |
| ((IObjectSymbol)base).coerce(TypeConstants.TYPE_MAP); |
| } |
| // Lists have no properties, even if they are also beans |
| else if (((IObjectSymbol)base).supportsCoercion(TypeConstants.TYPE_LIST)) |
| { |
| typeDesc = null; |
| } |
| else |
| { |
| typeDesc = ((IObjectSymbol)base).getTypeDescriptor(); |
| |
| } |
| |
| if (typeDesc != null) |
| { |
| symbolsList = typeDesc.getProperties(); |
| } |
| } |
| |
| return (ISymbol[]) symbolsList.toArray(ISymbol.EMPTY_SYMBOL_ARRAY); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jst.jsf.designtime.el.AbstractDTPropertyResolver#getProperty(org.eclipse.jst.jsf.context.symbol.ISymbol, int) |
| */ |
| public ISymbol getProperty(ISymbol base, int offset) |
| { |
| ITypeDescriptor typeDesc = null; |
| |
| if (offset < 0) |
| { |
| // should never be called with offset < 0 |
| throw new AssertionError("offsets must be >=0 to be valid"); //$NON-NLS-1$ |
| } |
| |
| // check for expected interface types per JSP.2.3.4 |
| if (base instanceof IObjectSymbol) |
| { |
| |
| final IObjectSymbol objSymbol = (IObjectSymbol) base; |
| typeDesc = objSymbol.getTypeDescriptor(); |
| |
| // per JSP.2.3.4, if instance of array (unconstrained in our terminology) |
| if (typeDesc.isArray()) |
| { |
| ISymbol arrayElement = typeDesc.getArrayElement(); |
| // reset the name |
| arrayElement.setName(base.getName()+"["+offset+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
| return arrayElement; |
| } |
| |
| // per JSP.2.3.4, if instance of list (unbounded in our terminology) |
| if (objSymbol.supportsCoercion(TypeConstants.TYPE_LIST)) |
| { |
| //typeDesc = objSymbol.coerce(TypeConstants.TYPE_LIST); |
| final EList<ValueType> args = new BasicEList<ValueType>(); |
| args.add(new ValueType(Signature.SIG_INT, ValueType.ASSIGNMENT_TYPE_RHS)); |
| return objSymbol.call("get", args, base.getName()+"["+offset+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| |
| // check unconstrained type |
| // if (typeDesc instanceof IBoundedTypeDescriptor) |
| // { |
| // // TODO: propertyId may need to change when supporting |
| // // template types |
| // if (((IBoundedTypeDescriptor)typeDesc).isUnboundedForType(TypeConstants.TYPE_BOXED_INTEGER)) |
| // { |
| // the most we know is that it could be an Object |
| // return ((IBoundedTypeDescriptor)typeDesc) |
| // .getUnboundedProperty |
| // (new Integer(offset), TypeConstants.TYPE_BOXED_INTEGER); |
| // final EList<Object> args = new BasicEList<Object>(); |
| // args.add(offset); |
| // return ((IBoundedTypeDescriptor)typeDesc) |
| // .call("get", args, base.getName()+"["+offset+"]"); |
| // } |
| // } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @param typeDesc |
| * @return the type descriptor's property iterator or empty list |
| * if null |
| */ |
| protected final Iterator getIterator(ITypeDescriptor typeDesc) |
| { |
| if (typeDesc != null) |
| { |
| return typeDesc.getProperties().iterator(); |
| } |
| return Collections.EMPTY_LIST.iterator(); |
| } |
| |
| /** |
| * Takes a key expression and factors it down to into all property segments it contains. |
| * Property segments occur mainly when String keys contain '.' characters, indicating that |
| * more one than property actually must be traversed to evaluate the whole expr. |
| * @param key |
| * @return an array containing all property segments of the key. If the key contains only |
| * one property, then this is returned a single element in the array |
| */ |
| protected final Object[] factorKey(Object key) |
| { |
| if (key instanceof String) |
| { |
| List segments = new ArrayList(); |
| |
| String stringKey = (String) key; |
| int nextPos = -1; |
| |
| while ((nextPos = stringKey.indexOf('.')) > -1) |
| { |
| segments.add(stringKey.substring(0, nextPos)); |
| stringKey = stringKey.substring(nextPos+1); |
| } |
| |
| if (stringKey != null && stringKey.length() > 0) |
| { |
| segments.add(stringKey); |
| } |
| |
| return segments.toArray(); |
| } |
| |
| return new Object[] {key}; |
| } |
| } |