blob: 6f9a0f8e1c4e12b458085ad2c0bf63a0a62fc459 [file] [log] [blame]
/*
* 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.netbeans.modules.web.el;
import com.sun.el.parser.*;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.ELException;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.platform.JavaPlatformManager;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.TypeUtilities.TypeNameOptions;
import org.netbeans.modules.web.el.completion.ELStreamCompletionItem;
import org.netbeans.modules.web.el.spi.ELVariableResolver.VariableInfo;
import org.netbeans.modules.web.el.spi.ImplicitObject;
import org.netbeans.modules.web.el.refactoring.RefactoringUtil;
import org.netbeans.modules.web.el.spi.ELPlugin;
import org.netbeans.modules.web.el.spi.ELVariableResolver;
import org.netbeans.modules.web.el.spi.Function;
import org.netbeans.modules.web.el.spi.ImplicitObjectType;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.modules.InstalledFileLocator;
/**
* Utility class for resolving elements/types for EL expressions.
*
* @author Erno Mononen
*/
public final class ELTypeUtilities {
private static final Logger LOG = Logger.getLogger(ELTypeUtilities.class.getName());
private static final String FACES_CONTEXT_CLASS = "javax.faces.context.FacesContext"; //NOI18N
private static final String UI_COMPONENT_CLASS = "javax.faces.component.UIComponent"; //NOI18N
private static final String STREAM_CLASS = "com.sun.el.stream.Stream"; //NOI18N
private static final FileObject EL_IMPL_JAR_FO = FileUtil.getArchiveRoot(FileUtil.toFileObject(
InstalledFileLocator.getDefault().locate("modules/ext/el-impl.jar", "org.netbeans.modules.libs.elimpl", false))); //NOI18N
private static final Map<Class<? extends Node>, Set<TypeKind>> TYPES = new HashMap<>();
static {
put(AstFloatingPoint.class, TypeKind.FLOAT, TypeKind.DOUBLE);
put(AstTrue.class, TypeKind.BOOLEAN);
put(AstFalse.class, TypeKind.BOOLEAN);
put(AstInteger.class, TypeKind.INT, TypeKind.SHORT, TypeKind.LONG);
}
private static void put(Class<? extends Node> node, TypeKind... kinds) {
Set<TypeKind> kindSet = new HashSet<>();
kindSet.addAll(Arrays.asList(kinds));
TYPES.put(node, kindSet);
}
private ELTypeUtilities() {
//do not create instancies
}
public static String getTypeNameFor(CompilationContext info, Element element) {
final TypeMirror tm = getTypeMirrorFor(info, element);
return info.info().getTypeUtilities().getTypeName(tm).toString();
}
public static Element getTypeFor(CompilationContext info, Element element) {
TypeMirror tm = getTypeMirrorFor(info, element);
return info.info().getTypes().asElement(tm);
}
public static List<Element> getSuperTypesFor(CompilationContext info, Element element) {
return getSuperTypesFor(info, element, null, null);
}
/**
*
* @param element
* @return a list of Element-s representing all the superclasses of the element.
* The list starts with the given element itself and ends with java.lang.Object
*/
public static List<Element> getSuperTypesFor(CompilationContext info, Element element, ELElement elElement, List<Node> rootToNode) {
final TypeMirror tm = getTypeMirrorFor(info, element, elElement, rootToNode);
List<Element> types = new ArrayList<>();
Deque<TypeMirror> deque = new ArrayDeque<>();
deque.add(tm);
while (!deque.isEmpty()) {
TypeMirror mirror = deque.pop();
if (mirror.getKind() == TypeKind.DECLARED) {
Element el = info.info().getTypes().asElement(mirror);
types.add(el);
if (el.getKind() == ElementKind.CLASS) {
TypeElement tel = (TypeElement) el;
TypeMirror superclass = tel.getSuperclass();
deque.add(superclass);
} else if (el.getKind() == ElementKind.INTERFACE) {
TypeElement tel = (TypeElement) el;
for (TypeMirror ifaceMirror : tel.getInterfaces()) {
deque.add(ifaceMirror);
}
}
}
}
return types;
}
/**
* Resolves the element for the given {@code target}.
* @param elem
* @param target
* @return the element or {@code null}.
*/
public static Element resolveElement(CompilationContext info, final ELElement elem, final Node target) {
return resolveElement(info, elem, target, Collections.<AstIdentifier, Node>emptyMap(), Collections.<VariableInfo>emptyList());
}
/**
* Resolves the element for the given {@code target}.
* @param elem
* @param target
* @return the element or {@code null}.
*/
public static Element resolveElement(CompilationContext info, final ELElement elem, final Node target, Map<AstIdentifier, Node> assignments, List<VariableInfo> variableInfos) {
TypeResolverVisitor typeResolver = new TypeResolverVisitor(info, elem, target, assignments, variableInfos);
elem.getNode().accept(typeResolver);
return typeResolver.getResult();
}
public static TypeMirror getReturnType(CompilationContext info, final ExecutableElement method) {
return getReturnType(info, method, null, null);
}
/**
* Gets the return type of the given {@code method}.
* @param method
* @return
*/
public static TypeMirror getReturnType(CompilationContext info, final ExecutableElement method, ELElement elElement, List<Node> rootToNode) {
TypeKind returnTypeKind = method.getReturnType().getKind();
if (returnTypeKind.isPrimitive()) {
return info.info().getTypes().getPrimitiveType(returnTypeKind);
} else if (returnTypeKind == TypeKind.VOID) {
return info.info().getTypes().getNoType(returnTypeKind);
} else if (returnTypeKind == TypeKind.TYPEVAR && rootToNode != null && elElement != null) {
return getReturnTypeForGenericClass(info, method, elElement, rootToNode);
} else {
return method.getReturnType();
}
}
public static TypeMirror getReturnTypeForGenericClass(CompilationContext info, final ExecutableElement method, ELElement elElement, List<Node> rootToNode) {
Node node = null;
for (int i = rootToNode.size() - 1; i > 0; i--) {
node = rootToNode.get(i);
if (node instanceof AstIdentifier) {
break;
}
}
if (node != null) {
Element element = ELTypeUtilities.resolveElement(info, elElement, node);
if (element == null) {
return method.getReturnType();
}
TypeMirror type = element.asType();
// interfaces are at the end of the List - first parameter has to be superclass
TypeMirror directSupertype = info.info().getTypes().directSupertypes(type).get(0);
if (directSupertype instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) directSupertype;
// index of involved type argument
int indexOfTypeArgument = -1;
// list of all type arguments
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
// search for the same method in the generic class
for (Element enclosedElement : declaredType.asElement().getEnclosedElements()) {
if (method.equals(enclosedElement)) {
TypeMirror returnType = ((ExecutableElement) enclosedElement).getReturnType();
// get index of type argument which is returned by involved method
indexOfTypeArgument = info.info().getElementUtilities().enclosingTypeElement(method).
getTypeParameters().indexOf(((TypeVariable) returnType).asElement());
break;
}
}
if (indexOfTypeArgument != -1 && indexOfTypeArgument < typeArguments.size()) {
return typeArguments.get(indexOfTypeArgument);
}
}
}
return method.getReturnType();
}
private static List<Node> getMethodParameters(Node methodNode) {
assert NodeUtil.isMethodCall(methodNode);
if (methodNode.jjtGetNumChildren() == 0) {
return Collections.emptyList();
}
Node methodArgs = methodNode.jjtGetChild(0);
for (int i = 0; i < methodNode.jjtGetNumChildren(); i++) {
Node currentNode = methodNode.jjtGetChild(i);
if (currentNode instanceof AstMethodArguments) {
methodArgs = currentNode;
break;
}
}
if (!(methodArgs instanceof AstMethodArguments)) {
return Collections.emptyList();
}
List<Node> parameters = new ArrayList<>();
for (int i = 0; i < methodArgs.jjtGetNumChildren(); i++) {
parameters.add(methodArgs.jjtGetChild(i));
}
return parameters;
}
/**
* Says whether the given method node and the element's method correspond.
* @param info context
* @param methodNode EL's method node
* @param method method element
* @return {@code true} if the method node correspond (param, naming) to the method element, {@code false} otherwise
*/
public static boolean isSameMethod(CompilationContext info, Node methodNode, ExecutableElement method) {
return isSameMethod(info, methodNode, method, false);
}
/**
* Says whether the given method node and the element's method correspond.
* @param info context
* @param methodNode EL's method node
* @param method method element
* @param includeSetter whether setters method should be considered as correct - <b>not precise, do not call it in refactoring</b>
* @return {@code true} if the method node correspond with the method element, {@code false} otherwise
*/
public static boolean isSameMethod(CompilationContext info, Node methodNode, ExecutableElement method, boolean includeSetter) {
String image = getMethodName(methodNode);
String methodName = method.getSimpleName().toString();
TypeMirror methodReturnType = method.getReturnType();
if (image == null) {
return false;
}
int methodParams = method.getParameters().size();
if (NodeUtil.isMethodCall(methodNode) &&
(methodName.equals(image) || RefactoringUtil.getPropertyName(methodName, methodReturnType).equals(image))) {
//now we are in AstDotSuffix or AstBracketSuffix
//lets check if the parameters are equal
List<Node> parameters = getMethodParameters(methodNode);
int methodNodeParams = parameters.size();
if (method.isVarArgs()) {
return methodParams == 1 ? true : methodNodeParams >= methodParams;
}
return (method.getParameters().size() == methodNodeParams && haveSameParameters(info, methodNode, method))
|| methodNodeParams == 0 && hasActionEventArgument(method);
}
if (methodNode instanceof AstDotSuffix) {
if (methodName.equals(image) || RefactoringUtil.getPropertyName(methodName, methodReturnType).equals(image)) {
if (methodNode.jjtGetNumChildren() > 0) {
for (int i = 0; i < method.getParameters().size(); i++) {
final VariableElement methodParameter = method.getParameters().get(i);
final Node methodNodeParameter = methodNode.jjtGetChild(i);
if (!isSameType(info, methodNodeParameter, methodParameter)) {
return false;
}
}
}
if (image.equals(methodName)) {
return true;
}
return method.isVarArgs()
? method.getParameters().size() == 1
: method.getParameters().isEmpty();
} else if (includeSetter && RefactoringUtil.getPropertyName(methodName, methodReturnType, true).equals(image)) {
// issue #225849 - we don't have additional information from the Facelet,
// believe the naming conventions. This is not used for refactoring actions.
return true;
}
}
return false;
}
public static TypeElement getElementForType(CompilationContext info, final String clazz) {
return info.info().getElements().getTypeElement(clazz);
}
public static List<String> getParameterNames(CompilationContext info, final ExecutableElement method) {
List<String> result = new ArrayList<>();
for (VariableElement param : method.getParameters()) {
result.add(param.getSimpleName().toString());
}
return result;
}
public static String getParametersAsString(CompilationContext info, ExecutableElement method) {
StringBuilder result = new StringBuilder();
for (VariableElement param : method.getParameters()) {
if (result.length() > 0) {
result.append(",");
}
String type = info.info().getTypeUtilities().getTypeName(param.asType()).toString();
result.append(type);
result.append(" ");
result.append(param.getSimpleName().toString());
}
if (result.length() > 0) {
result.insert(0, "(");
result.append(")");
}
return result.toString();
}
public static Collection<ImplicitObject> getImplicitObjects(CompilationContext info) {
return ELPlugin.Query.getImplicitObjects(info.file());
}
public static Collection<Function> getELFunctions(CompilationContext info) {
return ELPlugin.Query.getFunctions(info.file());
}
public static boolean isScopeObject(CompilationContext info, Node target) {
if (!(target instanceof AstIdentifier)) {
return false;
}
for (ImplicitObject each : getImplicitObjects(info)) {
if (each.getType() == ImplicitObjectType.SCOPE_TYPE
&& each.getName().equals(target.getImage())) {
return true;
}
}
return false;
}
public static boolean isRawObjectReference(CompilationContext info, Node target, boolean directly) {
// Parse tree for #{cc.attrs.muj} expression
//
// CompositeExpression
// DeferredExpression
// Value
// Identifier[cc]
// PropertySuffix[attrs]
// PropertySuffix[muj]
return isImplicitObjectReference(info, target, Arrays.asList(ImplicitObjectType.RAW), directly);
}
public static String getRawObjectName(Node root) {
List<Node> path = new AstPath(root).rootToLeaf();
for (int i = 0; i < root.jjtGetNumChildren(); i++) {
if ("cc".equals(path.get(i).getImage())) { //NOI18N
if ((i + 2) < root.jjtGetNumChildren()
&& "attrs".equals(path.get(i + 1).getImage())) { //NOI18N
return path.get(i + 2).getImage();
}
}
}
return null;
}
public static boolean isImplicitObjectReference(CompilationContext info, Node target, List<ImplicitObjectType> types, boolean directly) {
int repeation = directly ? 2 : Integer.MAX_VALUE;
while (target != null && repeation > 0) {
if (target instanceof AstIdentifier) {
for (ImplicitObject each : getImplicitObjects(info)) {
if (types.contains(each.getType()) && each.getName().equals(target.getImage())) {
return true;
}
}
}
target = NodeUtil.getSiblingBefore(target);
repeation--;
};
return false;
}
public static boolean isResourceBundleVar(CompilationContext info, Node target) {
if (!(target instanceof AstIdentifier)) {
return false;
}
ResourceBundles resourceBundles = ResourceBundles.get(info.file());
if (!resourceBundles.canHaveBundles()) {
return false;
}
String bundleVar = target.getImage();
return resourceBundles.isResourceBundleIdentifier(bundleVar, info.context());
}
/**
* Returns name of the method which is called by bracket call.
* @param bracketSuffixNode node of the type AstBracketSuffix
* @return method name without any quotes or apostrophes
*/
public static String getBracketMethodName(Node bracketSuffixNode) {
String nameInclQuot = bracketSuffixNode.jjtGetChild(0).getImage();
if (nameInclQuot.length() > 2) {
return nameInclQuot.substring(1, nameInclQuot.length() - 1);
} else {
return ""; //NOI18N
}
}
private static String getMethodName(Node methodNode) {
if (methodNode instanceof AstBracketSuffix) {
// method call in the brackets like bean['myCall']()
return getBracketMethodName(methodNode);
} else {
// method call after the dot like bean.myCall()
return methodNode.getImage();
}
}
private static TypeMirror getTypeMirrorFor(CompilationContext info, Element element) {
return getTypeMirrorFor(info, element, null, null);
}
private static TypeMirror getTypeMirrorFor(CompilationContext info, Element element, ELElement elElement, List<Node> rootToNode) {
if (element.getKind() == ElementKind.METHOD) {
return getReturnType(info, (ExecutableElement) element, elElement, rootToNode);
}
return element.asType();
}
private static boolean isValidatorMethod(CompilationContext info, ExecutableElement method) {
if (method.getParameters().size() != 3) {
return false;
}
VariableElement param1 = method.getParameters().get(0);
VariableElement param2 = method.getParameters().get(1);
CharSequence param1Type = getTypeName(info, param1.asType());
CharSequence param2Type = getTypeName(info, param2.asType());
return FACES_CONTEXT_CLASS.equals(param1Type) && UI_COMPONENT_CLASS.equals(param2Type);
}
private static CharSequence getTypeName(CompilationContext info, TypeMirror type) {
return info.info().getTypeUtilities().getTypeName(type, TypeNameOptions.PRINT_FQN);
}
private static boolean haveSameParameters(CompilationContext info, Node methodNode, ExecutableElement method) {
List<Node> methodNodeParameters = getMethodParameters(methodNode);
for (int i = 0; i < methodNodeParameters.size(); i++) {
Node paramNode = methodNodeParameters.get(i);
if (!isSameType(info, paramNode, method.getParameters().get(i))) {
return false;
}
}
return true;
}
private static boolean hasActionEventArgument(ExecutableElement method) {
List<? extends VariableElement> parameters = method.getParameters();
// check one parameter ActionEvent method
if (parameters.size() != 1) {
return false;
}
return "javax.faces.event.ActionEvent".equals(parameters.get(0).asType().toString()); //NOI18N
}
private static boolean isSameType(CompilationContext info, final Node paramNode, final VariableElement param) {
TypeKind paramKind = param.asType().getKind();
if (!paramKind.isPrimitive()) {
// try unboxing
try {
PrimitiveType unboxedType = info.info().getTypes().unboxedType(param.asType());
paramKind = unboxedType.getKind();
} catch (IllegalArgumentException iae) {
// not unboxable (isn't there a way to check this before trying to unbox??)
}
}
if (TYPES.containsKey(paramNode.getClass())) {
return TYPES.get(paramNode.getClass()).contains(paramKind);
}
if (paramNode instanceof AstString) {
CharSequence typeName = info.info().getTypeUtilities().getTypeName(param.asType(), TypeNameOptions.PRINT_FQN);
if (String.class.getName().contentEquals(typeName)) {
return true;
}
if (paramKind == TypeKind.DECLARED) {
return isSubtypeOf(info, param.asType(), "java.lang.Enum"); // NOI18N
}
return false;
}
// the ast param is an object whose real type we don't know
// would need to further type inference for more exact matching
return true;
}
/**
* Gets the element matching the given name from the given enclosing class.
* @param name the name of the element to find.
* @param enclosing
* @return
*/
private static ExecutableElement getElementForProperty(CompilationContext info, Node property, Element enclosing) {
for (Element element : getSuperTypesFor(info, enclosing)) {
for (ExecutableElement each : ElementFilter.methodsIn(element.getEnclosedElements())) {
// we're only interested in public methods
if (!each.getModifiers().contains(Modifier.PUBLIC)) {
continue;
}
if (isSameMethod(info, property, each, true)) {
return each;
}
}
}
return null;
}
private static Element getIdentifierType(CompilationContext info, AstIdentifier identifier, ELElement element) {
if (info.file() == null) {
// Strange case - file was deleted? Try to find out whether it's invalid.
FileObject fileObject = element.getSnapshot().getSource().getFileObject();
LOG.log(Level.WARNING, "FileObject to resolve doesn''t exist: {0}, isValid: {1}",
new Object[]{fileObject, fileObject != null ? fileObject.isValid() : "null"});
return null;
}
String tempClass = null;
// try implicit objects first
for (ImplicitObject implicitObject : getImplicitObjects(info)) {
if (implicitObject.getName().equals(identifier.getImage())) {
if (implicitObject.getClazz() == null || implicitObject.getClazz().isEmpty()) {
// the identiefier represents an implicit object whose type we don't know
// tempClass = Object.class.getName();
} else {
tempClass = implicitObject.getClazz();
}
break;
}
}
if (tempClass == null) {
// managed beans
tempClass = ELVariableResolvers.findBeanClass(info, identifier.getImage(), element.getSnapshot().getSource().getFileObject());
}
if (tempClass != null) {
return info.info().getElements().getTypeElement(tempClass);
}
// probably a variable
int offset = element.getOriginalOffset().getStart() + identifier.startOffset();
Collection<ELVariableResolver.VariableInfo> vis = ELVariableResolvers.getVariables(info, element.getSnapshot(), offset);
for (ELVariableResolver.VariableInfo vi : vis) {
if (identifier.getImage().equals(vi.name)) {
try {
ELPreprocessor elp = new ELPreprocessor(vi.expression, ELPreprocessor.XML_ENTITY_REFS_CONVERSION_TABLE);
Node expressionNode = ELParser.parse(elp);
if (expressionNode != null) {
return getReferredType(info, expressionNode, element.getSnapshot().getSource().getFileObject());
}
} catch (ELException e) {
//invalid expression
}
}
}
return null;
}
/**
* Resolves the given variable type
* @param vi the variable to be resolved
* @return source Element representing the variable
*/
public static Element getReferredType(CompilationContext info, VariableInfo vi, FileObject context) {
//resolved variable
if (vi.clazz != null) {
return info.info().getElements().getTypeElement(vi.clazz);
}
//unresolved variable
assert vi.expression != null;
try {
ELPreprocessor elp = new ELPreprocessor(vi.expression, ELPreprocessor.XML_ENTITY_REFS_CONVERSION_TABLE);
Node expressionNode = ELParser.parse(elp);
if (expressionNode != null) {
return getReferredType(info, expressionNode, context);
}
} catch (ELException e) {
//invalid expression
}
return null;
}
/**
* @return the element for the type that that given {@code expression} refers to, i.e.
* the return type of the last method in the expression.
*
* The method can ONLY be used for resolved expressions, i.e. the base object must be a known bean,
* not a variable!
*/
public static Element getReferredType(final CompilationContext info, Node expression, final FileObject context) {
final Element[] result = new Element[1];
expression.accept(new NodeVisitor() {
@Override
public void visit(Node node) throws ELException {
if (node instanceof AstIdentifier) {
Node parent = node.jjtGetParent();
String beanClass = ELVariableResolvers.findBeanClass(info, node.getImage(), context);
if (beanClass == null) {
return;
}
Element enclosing = info.info().getElements().getTypeElement(beanClass);
if (enclosing == null) {
//no such class on the classpath
return;
}
ExecutableElement method, lastResolved = null;
for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
Node current = parent.jjtGetChild(i);
if (enclosing != null && (current instanceof AstDotSuffix || NodeUtil.isMethodCall(current))) {
method = getElementForProperty(info, current, enclosing);
if (method == null) {
continue;
} else if (ELStreamCompletionItem.STREAM_METHOD.equals(method.getSimpleName().toString())
&& isIterableElement(info, enclosing)) {
// method is resolved - in case of JDK8 and stream used over iterable
break;
} else {
// issue #243833 - use last resolved method if any
lastResolved = method;
}
enclosing = info.info().getTypes().asElement(getReturnType(info, method));
}
}
if (lastResolved == null) {
return;
}
TypeMirror returnType = getReturnType(info, lastResolved);
//XXX: works just for generic collections, i.e. the assumption is
// that variables refer to collections, which is not always the case
if (returnType.getKind() == TypeKind.DECLARED) {
if (isSubtypeOf(info, returnType, "java.lang.Iterable")) { //NOI18N
List<? extends TypeMirror> typeArguments = ((DeclaredType) returnType).getTypeArguments();
for (TypeMirror arg : typeArguments) {
result[0] = info.info().getTypes().asElement(arg);
return;
}
//use the returned type itself
result[0] = info.info().getTypes().asElement(returnType);
}
} else if (returnType.getKind() == TypeKind.ARRAY) {
TypeMirror componentType = ((ArrayType) returnType).getComponentType();
result[0] = info.info().getTypes().asElement(componentType);
}
}
}
});
return result[0];
}
/**
* Whether the given node represents static {@link Iterable} field where can be used operators.
* @param ccontext compilation context
* @param node node to examine
* @return {@code true} if the object is static {@link Iterable} field, {@code false} otherwise
* @since 1.26
*/
public static boolean isStaticIterableElement(CompilationContext ccontext, Node node) {
return (node instanceof AstListData || node instanceof AstMapData);
}
/**
* Whether the given node represents access to the collection or array.
* @param ccontext compilation context
* @param node node to examine
* @return {@code true} if the node means access to collection or array, {@code false} otherwise
* @since 1.34
*/
public static boolean isAccessIntoCollection(CompilationContext ccontext, Node node) {
return (node instanceof AstInteger);
}
/**
* Whether the given node represents static {@link Iterable} field where can be used operators.
* @param ccontext compilation context
* @param element element to examine
* @return {@code true} if the object is static {@link Iterable} field, {@code false} otherwise
* @since 1.26
*/
public static boolean isIterableElement(CompilationContext ccontext, Element element) {
if (element.getKind() == ElementKind.CLASS) {
return isSubtypeOf(ccontext, element.asType(), "java.lang.Iterable"); //NOI18N
} else if (element.getKind() == ElementKind.METHOD) {
TypeMirror returnType = ELTypeUtilities.getReturnType(ccontext, (ExecutableElement) element);
if (returnType.getKind() == TypeKind.ARRAY
|| isSubtypeOf(ccontext, returnType, "java.lang.Iterable")) { //NOI18N
return true;
}
} else if (element.getKind() == ElementKind.INTERFACE) {
return isSubtypeOf(ccontext, element.asType(), "java.lang.Iterable"); //NOI18N
}
return false;
}
/**
* Whether the given node represents Map field.
* @param ccontext compilation context
* @param element element to examine
* @return {@code true} if the element extends {@link Map} interface, {@code false} otherwise
* @since 1.28
*/
public static boolean isMapElement(CompilationContext ccontext, Element element) {
return isSubtypeOf(ccontext, element.asType(), "java.util.Map"); //NOI18N
}
/**
* Gets {@code ClasspathInfo} extended for the el-impl.jar. It guarantees to find Stream class on the classpath.
* @param file file to be used for getting classpaths
* @return extended classpath for the el-impl.jar
* @since 1.39
*/
public static ClasspathInfo getElimplExtendedCPI(FileObject file) {
ClassPath bootPath = ClassPath.getClassPath(file, ClassPath.BOOT);
if (bootPath == null) {
bootPath = JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries();
}
ClassPath compilePath = ClassPath.getClassPath(file, ClassPath.COMPILE);
if (compilePath == null) {
compilePath = ClassPathSupport.createClassPath(new URL[0]);
}
ClassPath srcPath = ClassPath.getClassPath(file, ClassPath.SOURCE);
return ClasspathInfo.create(
bootPath,
ClassPathSupport.createProxyClassPath(compilePath, ClassPathSupport.createClassPath(EL_IMPL_JAR_FO)),
srcPath);
}
/**
* Gets information whether given type is inherits particular type specified by FQN.
* @param info compilation context
* @param tm type to be check for inheritance from typeName
* @param typeName parent class to be inherited from
* @return {@code true} if tm inherits the given typeName, {@code false} otherwise
*/
public static boolean isSubtypeOf(CompilationContext info, TypeMirror tm, CharSequence typeName) {
Element element = info.info().getElements().getTypeElement(typeName);
if (element == null) {
return false;
}
TypeMirror type = element.asType(); //NOI18N
TypeMirror erasedType = info.info().getTypes().erasure(type);
TypeMirror tmErasure = info.info().getTypes().erasure(tm);
return info.info().getTypes().isSubtype(tmErasure, erasedType);
}
private static TypeElement getTypeFor(CompilationContext info, String clazz) {
return info.info().getElements().getTypeElement(clazz);
}
private static class TypeResolverVisitor implements NodeVisitor {
private final ELElement elem;
private final Node target;
private final Map<AstIdentifier, Node> assignments;
private final List<ELVariableResolver.VariableInfo> variableInfos;
private Element result;
private CompilationContext info;
public TypeResolverVisitor(CompilationContext info, ELElement elem, Node target, Map<AstIdentifier, Node> assignments, List<ELVariableResolver.VariableInfo> variableInfos) {
this.info = info;
this.elem = elem;
this.target = target;
this.assignments = assignments;
this.variableInfos = variableInfos;
}
public Element getResult() {
return result;
}
@Override
public void visit(Node node) {
Element enclosing = null;
// look for possible assignments to the identifier
Node evalNode;
if (node instanceof AstIdentifier && assignments.containsKey((AstIdentifier) node)) {
evalNode = assignments.get((AstIdentifier) node);
} else {
evalNode = node;
}
// traverses AST resolving types for each property starting from
// an identifier until the target is found
if (evalNode instanceof AstIdentifier) {
enclosing = getIdentifierType(info, (AstIdentifier) evalNode, elem);
if (enclosing != null) {
if (node.equals(target)) {
result = enclosing;
return;
}
Node parent = node.jjtGetParent();
for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
Node child = parent.jjtGetChild(i);
if (child instanceof AstDotSuffix || NodeUtil.isMethodCall(child)) {
Element propertyType = getElementForProperty(info, child, enclosing);
if (propertyType == null) {
// special case handling for scope objects; their types don't help
// in resolving the beans they contain. The code below handles cases
// like sessionScope.myBean => sessionScope is in position parent.jjtGetChild(i - 1)
if (i > 0 && isScopeObject(info, parent.jjtGetChild(i - 1))) {
final String clazz = ELVariableResolvers.findBeanClass(info, child.getImage(), elem.getSnapshot().getSource().getFileObject());
if (clazz == null) {
return;
}
// it's a managed bean in a scope
propertyType = getTypeFor(info, clazz);
}
// cc.attrs.<xxx> resolving - XXX better way how to detect this special case?
if ("cc".equals(node.getImage())) {
if ("attrs".equals(child.getImage())) {
propertyType = info.info().getElements().getTypeElement("java.lang.Object"); //NOI18N
} else {
for (VariableInfo property : variableInfos) {
if (child.getImage().equals(property.name)) {
if (property.clazz == null) {
propertyType = info.info().getElements().getTypeElement("java.lang.Object"); //NOI18N
} else {
propertyType = info.info().getElements().getTypeElement(property.clazz);
}
}
}
}
}
// maps
if (ELTypeUtilities.isMapElement(info, enclosing)) {
result = info.info().getElements().getTypeElement("java.lang.Object"); //NOI18N
return;
}
// stream method
if (ELTypeUtilities.isIterableElement(info, enclosing)) {
propertyType = enclosing = info.info().getElements().getTypeElement(STREAM_CLASS);
}
} else {
// issue #244065 - in case of JDK8 and Collection, return the EL's Stream class
if (propertyType instanceof ExecutableElement) {
String returnType = ((ExecutableElement) propertyType).getReturnType().toString();
if ("java.util.stream.Stream<E>".equals(returnType)) { //NOI18N
propertyType = info.info().getElements().getTypeElement(STREAM_CLASS);
}
}
}
if (propertyType == null) {
return;
}
if (child.equals(target)) {
result = propertyType;
} else if (propertyType.getKind() == ElementKind.METHOD) {
final ExecutableElement method = (ExecutableElement) propertyType;
TypeMirror returnType = getReturnType(info, method, elem, NodeUtil.getRootToNode(elem, target));
if (returnType.getKind() == TypeKind.ARRAY) {
// for array try to look like Iterable (operators for array return type)
enclosing = info.info().getElements().getTypeElement("java.lang.Iterable"); //NOI18N
} else {
if (isAccessIntoCollection(info, target) && returnType instanceof DeclaredType) {
List<? extends TypeMirror> typeArguments = ((DeclaredType) returnType).getTypeArguments();
if (!typeArguments.isEmpty()) {
enclosing = info.info().getTypes().asElement(typeArguments.get(0));
result = enclosing;
}
} else {
enclosing = info.info().getTypes().asElement(returnType);
}
}
if (enclosing == null) {
return;
}
} else {
enclosing = propertyType;
}
}
}
}
} else if (evalNode instanceof AstListData || evalNode instanceof AstMapData) {
Node parent = node.jjtGetParent();
for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
Node child = parent.jjtGetChild(i);
if (child instanceof AstDotSuffix) {
if (ELStreamCompletionItem.STREAM_METHOD.equals(child.getImage())) {
enclosing = info.info().getElements().getTypeElement(STREAM_CLASS);
} else {
if (enclosing != null) {
ExecutableElement propertyType = getElementForProperty(info, child, enclosing);
if (target.getImage() != null && target.getImage().equals(child.getImage())) {
if (propertyType != null) {
result = propertyType;
}
return;
} else {
if (propertyType != null) {
enclosing = getTypeFor(info, propertyType.getReturnType().toString());
}
}
}
}
// finish when the target matches property
if (target.getImage() != null && target.getImage().equals(child.getImage())) {
return;
}
}
}
}
}
}
}