/******************************************************************************* * Copyright (c) 2008 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: * Matthias Fuessel -- extracted from https://bugs.eclipse.org/bugs/show_bug.cgi?id=215461 * Cameron Bateman/Oracle - integrated and moved data table code here. * ********************************************************************************/ package org.eclipse.jst.jsf.designtime.symbols; import java.util.Arrays; import java.util.HashMap; import org.eclipse.core.resources.IFile; import org.eclipse.emf.common.util.ECollections; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.Signature; import org.eclipse.jst.jsf.common.internal.types.TypeConstants; import org.eclipse.jst.jsf.common.internal.types.ValueType; import org.eclipse.jst.jsf.common.util.TypeUtil; import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext; import org.eclipse.jst.jsf.context.symbol.ERuntimeSource; import org.eclipse.jst.jsf.context.symbol.IBeanInstanceSymbol; import org.eclipse.jst.jsf.context.symbol.IComponentSymbol; import org.eclipse.jst.jsf.context.symbol.IInstanceSymbol; import org.eclipse.jst.jsf.context.symbol.IJavaTypeDescriptor2; import org.eclipse.jst.jsf.context.symbol.IMapTypeDescriptor; import org.eclipse.jst.jsf.context.symbol.IObjectSymbol; import org.eclipse.jst.jsf.context.symbol.ISymbol; import org.eclipse.jst.jsf.context.symbol.InitializedSymbolFactory; import org.eclipse.jst.jsf.context.symbol.SymbolFactory; import org.eclipse.jst.jsf.context.symbol.internal.util.IObjectSymbolBasedValueType; import org.eclipse.jst.jsf.designtime.resolver.IStructuredDocumentSymbolResolverFactory; import org.eclipse.jst.jsf.designtime.resolver.StructuredDocumentSymbolResolverFactory; import org.eclipse.jst.jsf.validation.internal.IJSFViewValidator.IValidationReporter; import org.eclipse.jst.jsf.validation.internal.IJSFViewValidator.ReporterAdapter; import org.eclipse.jst.jsf.validation.internal.el.ELExpressionValidator; import org.eclipse.jst.jsf.validation.internal.el.IExpressionSemanticValidator; /** * Factory for creating symbols for variables/message bundles that are * contributed by components/tags. This Factory can be used by descendants of * {@link org.eclipse.jst.jsf.context.symbol.source.AbstractContextSymbolFactory} in order to create the symbols they want * to contribute. * * It provides also methods for getting the resulting {@link ValueType} from an * EL expression and the "row" type signature from an DataModel/Array/List * {@link ValueType}. * * NOTE: C.B March 26, 2008 -- commented out portions are left for the future * because the rely on enhancements to the symbol model that could not be made * in the current release. Other portions of the original ComponentSymbolFactory * have been moved to {@link org.eclipse.jst.jsf.context.symbol.InitializedSymbolFactory} * This reflects the split between things deemed to be generic EL (put in common * plugin) and things deemed to be JSF-specific (put here). * */ public final class JSFSymbolFactory extends InitializedSymbolFactory { /** * @param elText The EL expression text. Must not be null * @param elContext The document context pointing to elText in the source document. Must not be null * @param file The workspace resource that contains elText. Must not be null. * @param symbolResolverFactory * @return the value expression resolved from elText or null if it cannot * be resolved or elText doesn't resolve to value expression (i.e. is a method expression) */ public ValueType getValueTypeFromEL(final String elText, final IStructuredDocumentContext elContext, final IFile file, IStructuredDocumentSymbolResolverFactory symbolResolverFactory) { assert elText != null; assert elContext != null; assert file != null; // TODO investigate if infinite recursion; use ASTResolver instead?? final IValidationReporter reporter = new ReporterAdapter(); final ELExpressionValidator validator = new ELExpressionValidator( elContext, elText, symbolResolverFactory, reporter); validator.validateXMLNode(); final IExpressionSemanticValidator semValidator = validator .getSemanticValidator(); if (semValidator != null && semValidator.getExpressionType() instanceof ValueType) { return (ValueType) semValidator.getExpressionType(); } return null; } /** * Convenience for {@link JSFSymbolFactory#getValueTypeFromEL(String, IStructuredDocumentContext, IFile, IStructuredDocumentSymbolResolverFactory)} * using StructuredDocumentSymbolResolverFactory.getInstance(). * * @param elText * @param elContext * @param file * @return a ValueType or null */ public ValueType getValueTypeFromEL(final String elText, final IStructuredDocumentContext elContext, final IFile file) { return getValueTypeFromEL(elText, elContext, file, StructuredDocumentSymbolResolverFactory.getInstance()); } /** * @param type * @return <code>true</code>, if the given {@link ValueType} represents * an instance of <code>javax.faces.model.DataModel</code> */ private boolean isFacesDataModel(ValueType type) { return type.isInstanceOf(TypeConstants.TYPE_DATA_MODEL); } /** * Tries to guess the row type of a <code>javax.faces.DataModel</code> * instance. This will only work if <code>type</code> is a descendant that * narrows down the return type of "getRowData()". * * @param type * @return the row type of the given DataModel. Will return * <code>null</code> if <code>type</code> is no DataModel or if * nothing more specific than <code>Object</code> can be * determined */ public String getRowSignatureFromDataModel(ValueType type) { if (type instanceof IObjectSymbolBasedValueType) { ISymbol resSymbol = ((IObjectSymbolBasedValueType) type) .getSymbol().call( "getRowData", ECollections.emptyEList(), "res"); //$NON-NLS-1$ //$NON-NLS-2$ if (resSymbol != null && resSymbol instanceof IObjectSymbol) { // TODO full signature final String signature = ((IObjectSymbol) resSymbol) .getTypeDescriptor().getTypeSignature(); if (!TypeConstants.TYPE_JAVAOBJECT.equals(signature)) { return signature; } } } return null; } /** * @param type * @return <code>true</code>, if <code>type</code> is a collection or * array */ public boolean isContainerType(ValueType type) { return type.isArray() || type.isInstanceOf(TypeConstants.TYPE_COLLECTION); } /** * @param type - * the type of the <code>value</code> property * @return the type signature of the row variable for every type that * <code>UIData</code> takes as <code>value</code> property. * Will return <code>null</code> if no type or nothing more * specific than <code>Object</code> can be determined */ public String getRowSignatureFromValueType(ValueType type) { if (isContainerType(type)) { return getElementSignatureFromContainerType(type); } if (isFacesDataModel(type)) { return getRowSignatureFromDataModel(type); } // Otherwise, according to jsf spec, treat value as single row: // TODO full signature return type.getSignature(); } /** * @param symbolName The name of the symbol to be created. Must not be null * @param signature The type signature of the array type. Must not be null * @param source the runtime source * @param javaProject must not be null * @return a symbol based approximating an implicit DataModel wrapper for an array */ public final ISymbol createArraySymbol(final String symbolName, final String signature, final ERuntimeSource source, final IJavaProject javaProject) { assert symbolName != null; assert signature != null; assert javaProject != null; final String arrayElementType = Signature.getElementType(signature); final int arrayCount = Signature.getArrayCount(signature); String adjustedSignature = null; // if it is a single array nesting, then it will just be the element type, // but if it is a multi-dim array, then the scalar element will be an array // with one less nesting level. It's a strange corner case to have an implicit // array of something as a row type, but it is a valid case. I suppose // it may be happen if you want to have tables of tables in which the nested tables // in turn use nested variables as their type... if (arrayCount > 0) { adjustedSignature = Signature.createArraySignature(arrayElementType, arrayCount-1); } else { adjustedSignature = arrayElementType; } return createScalarSymbol(symbolName, adjustedSignature, source, javaProject); } /** * @param symbolName The name of the symbol to create. Must not be null. * @param signature The fully resolved type signature of the scalar. Must not be null. * @param source * @param javaProject The JavaProject whose classpath is to be used to resolve type information for signture. Must not be null. * @return a symbol approximating a scalar object DataModel wrapper. The row variable for the * data model becomes of type signature */ public final ISymbol createScalarSymbol(final String symbolName, final String signature, final ERuntimeSource source, final IJavaProject javaProject) { assert symbolName != null; assert signature != null; assert javaProject != null; final String elementType = Signature.getElementType(signature); IJavaTypeDescriptor2 desc = SymbolFactory.eINSTANCE.createIJavaTypeDescriptor2(); final int arrayCount = Signature.getArrayCount(signature); if (arrayCount > 0) { desc.setArrayCount(arrayCount); } IType type = TypeUtil.resolveType(javaProject, elementType); if (type != null) { desc.setType(type); } else { desc.setTypeSignatureDelegate(Signature.getTypeErasure(signature)); } desc.getTypeParameterSignatures().addAll(Arrays.asList(Signature.getTypeArguments(signature))); IComponentSymbol symbol = SymbolFactory.eINSTANCE.createIComponentSymbol(); symbol.setName(symbolName); symbol.setTypeDescriptor(desc); symbol.setRuntimeSource(ERuntimeSource.TAG_INSTANTIATED_SYMBOL_LITERAL); return symbol; } /** * @param symbolName The name of the symbol to create. Must not be null. * @param valueType The value expression representing the implicit list. The signature * on the valueType must be a list. Must not be null. * @param source * @param description * @param javaProject The JavaProject whose classpath will be used to resolve types. Must not be null. * * @return a symbol that approximates as best as possible an implicit DataModel for java.util.List value expressions. If the List has * resolvable Java 5 type arguments, then a scalar symbol will be created * using this type information. If it is a raw type, then * createDefaultSymbol() is called */ public final ISymbol createFromList(String symbolName, ValueType valueType, ERuntimeSource source, String description, IJavaProject javaProject) { assert symbolName != null; assert valueType != null; assert javaProject != null; assert TypeConstants.TYPE_LIST.equals(valueType.getSignature()); final String[] typeArguments = valueType.getTypeArguments(); if (typeArguments != null && typeArguments.length > 0) { // a list has a single type argument final String typeArg = typeArguments[0]; if (Signature.getTypeSignatureKind(typeArg) == Signature.CLASS_TYPE_SIGNATURE) { return createScalarSymbol(symbolName, typeArg, source, javaProject); } } // if no resolvable type signatures, do the default thing return createDefaultSymbol(symbolName, ERuntimeSource.TAG_INSTANTIATED_SYMBOL_LITERAL, description); } /** * @param symbolName The name of the symbol to create. Must not be null * @param source * @param description * @return a default symbol that eliminates bogus warnings for this dataTable's * row variable in cases where something better is resolvable. Note that this is * not ideal, since will result in any property being accepted on the variable with * this name. */ public final ISymbol createDefaultSymbol(final String symbolName, final ERuntimeSource source, final String description) { assert symbolName != null; final IMapTypeDescriptor typeDesc = SymbolFactory.eINSTANCE.createIBoundedMapTypeDescriptor(); // empty map source typeDesc.setMapSource(new HashMap()); final IComponentSymbol symbol = SymbolFactory.eINSTANCE.createIComponentSymbol(); symbol.setName(symbolName); symbol.setTypeDescriptor(typeDesc); symbol.setDetailedDescription(description); symbol.setRuntimeSource(source); return symbol; } /** * FUTURE use: added to support future API feature. Should not be used. * @param symbolName * @param bundleName * @param project * @return an instance symbol for the message bundle */ public IInstanceSymbol createMessageBundleSymbol(final String symbolName, final String bundleName, IJavaProject project) { // FUTURE USE throw new UnsupportedOperationException("see https://bugs.eclipse.org/bugs/show_bug.cgi?id=215461"); //$NON-NLS-1$ } /** * FUTURE use: added to support future API feature. Should not be used. * @param symbolName * @param fullyQualifiedName * @param source * @param description * @param javaProject * @return an instance symbol for the message bundle */ public IBeanInstanceSymbol createManagedBeanSymbol(final String symbolName, final String fullyQualifiedName, ERuntimeSource source, final String description, final IJavaProject javaProject) { // FUTURE USE throw new UnsupportedOperationException("see https://bugs.eclipse.org/bugs/show_bug.cgi?id=215461"); //$NON-NLS-1$ } // / /** // * @param symbolName // * @param signature // * @param javaProject // * @return a symbol for a bean // */ // public IComponentBeanSymbol createBeanSymbolFromSignature( // final String symbolName, final String signature, // final IJavaProject javaProject) // { // IJavaTypeDescriptor2 desc = createTypeDescriptorFromSignature( // signature, javaProject); // // IComponentBeanSymbol symbol = SymbolFactory.eINSTANCE // .createIComponentBeanSymbol(); // symbol.setName(symbolName); // symbol.setTypeDescriptor(desc); // symbol.setRuntimeSource(ERuntimeSource.TAG_INSTANTIATED_SYMBOL_LITERAL); // return symbol; // } // /** // * @param symbolName // * @param bundleName // * @param project // * @return a symbol for a message bundle // */ // public IMessageBundleSymbol createMessageBundleSymbolFromBundleName(final String symbolName, final String bundleName, IJavaProject project) { // final IMessageBundleTypeDescriptor typeDesc = SymbolFactory.eINSTANCE.createIMessageBundleTypeDescriptor(); // try { // Map mapSource = ResourceBundleMapSourceFactory.getResourceBundleMapSource(project.getProject(), bundleName); // typeDesc.setMapSource(mapSource); // } catch (JavaModelException e) { // JSFCorePlugin.log(e, "Error creating resource map for bundle '"bundleName "'"); //$NON-NLS-1$ //$NON-NLS-2$ // } catch (IOException e) { // JSFCorePlugin.log(e, "Error creating resource map for bundle '"bundleName "'"); //$NON-NLS-1$ //$NON-NLS-2$ // } catch (CoreException e) { // JSFCorePlugin.log(e, "Error creating resource map for bundle '"bundleName "'"); //$NON-NLS-1$ //$NON-NLS-2$ // } // final IMessageBundleSymbol symbol = SymbolFactory.eINSTANCE.createIMessageBundleSymbol(); // symbol.setName(symbolName); // symbol.setTypeDescriptor(typeDesc); // symbol.setDetailedDescription(NLS.bind(Messages.getString("ComponentSymbolFactory.IMessageBundleSymbol.detailedDescription"), bundleName)); //$NON-NLS-1$ // return symbol; // } // /** // * Sets the symbol to be defined only inside the given element // * // * @param symbol // * @param slement // */ // public void setDefinitionRange(final IComponentVar symbol, // final Element slement) // { // if (slement instanceof IndexedRegion) // { // final IndexedRegion defRegion = (IndexedRegion) slement; // final int defOffset = defRegion.getStartOffset(); // final int defLength = defRegion.getLength(); // symbol.setDefinitionRange(defOffset, defLength); // } // } }