| /********************************************************************* |
| * Copyright (c) 2009, 2012 SpringSource, a division of VMware, Inc. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| **********************************************************************/ |
| |
| package org.eclipse.virgo.ide.bundlor.jdt.core; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.jdt.core.dom.ASTVisitor; |
| import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; |
| import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; |
| import org.eclipse.jdt.core.dom.CastExpression; |
| import org.eclipse.jdt.core.dom.CatchClause; |
| import org.eclipse.jdt.core.dom.ClassInstanceCreation; |
| import org.eclipse.jdt.core.dom.EnumDeclaration; |
| import org.eclipse.jdt.core.dom.FieldDeclaration; |
| import org.eclipse.jdt.core.dom.IBinding; |
| import org.eclipse.jdt.core.dom.IMethodBinding; |
| import org.eclipse.jdt.core.dom.IPackageBinding; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| import org.eclipse.jdt.core.dom.IVariableBinding; |
| import org.eclipse.jdt.core.dom.ImportDeclaration; |
| import org.eclipse.jdt.core.dom.InstanceofExpression; |
| import org.eclipse.jdt.core.dom.MarkerAnnotation; |
| import org.eclipse.jdt.core.dom.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.MethodInvocation; |
| import org.eclipse.jdt.core.dom.Modifier; |
| import org.eclipse.jdt.core.dom.Name; |
| import org.eclipse.jdt.core.dom.NormalAnnotation; |
| import org.eclipse.jdt.core.dom.QualifiedName; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.SimpleType; |
| import org.eclipse.jdt.core.dom.SingleMemberAnnotation; |
| import org.eclipse.jdt.core.dom.TypeDeclaration; |
| import org.eclipse.virgo.bundlor.support.partialmanifest.PartialManifest; |
| |
| /** |
| * {@link MethodVisitor} that is based on JDT's AST |
| * </p> |
| * <strong>Concurrent Semantics</strong><br /> |
| * Not thread safe. |
| * |
| * @author Christian Dupuis |
| * @author Glyn Normington |
| */ |
| public class ArtifactAnalyserTypeVisitor extends ASTVisitor { |
| |
| private final PartialManifest partialManifest; |
| |
| private final Set<String> referencedTypes = new HashSet<String>(); |
| |
| private String typePackage; |
| |
| public ArtifactAnalyserTypeVisitor(PartialManifest model) { |
| this.partialManifest = model; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(CastExpression node) { |
| recordTypeBinding(node.resolveTypeBinding()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(CatchClause node) { |
| recordTypeBinding(node.getException().getType().resolveBinding()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(ClassInstanceCreation node) { |
| recordTypeBinding(node.resolveTypeBinding()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(FieldDeclaration node) { |
| recordTypeBinding(node.getType().resolveBinding()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(ImportDeclaration node) { |
| if (node.resolveBinding() != null && !node.resolveBinding().isRecovered()) { |
| IBinding binding = node.resolveBinding(); |
| if (binding instanceof IPackageBinding) { |
| this.partialManifest.recordReferencedPackage(((IPackageBinding) binding).getName()); |
| } else if (binding instanceof ITypeBinding) { |
| recordTypeBinding((ITypeBinding) binding); |
| } |
| } else { |
| if (!node.isOnDemand() && !node.isStatic()) { |
| Name importElementName = node.getName(); |
| recordFullyQualifiedName(importElementName.getFullyQualifiedName()); |
| } else if (node.isOnDemand() && !node.isStatic()) { |
| Name importElementName = node.getName(); |
| this.partialManifest.recordReferencedPackage(importElementName.getFullyQualifiedName()); |
| } else if (!node.isOnDemand() && node.isStatic()) { |
| Name importElementName = node.getName(); |
| String fqn = importElementName.getFullyQualifiedName().substring(0, importElementName.getFullyQualifiedName().lastIndexOf('.')); |
| recordFullyQualifiedName(fqn); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(InstanceofExpression node) { |
| recordTypeBinding(node.getRightOperand().resolveBinding()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(MarkerAnnotation node) { |
| ITypeBinding binding = node.resolveTypeBinding(); |
| recordTypeBinding(binding); |
| this.partialManifest.recordUsesPackage(this.typePackage, getPackageBinding(binding).getName()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(MethodDeclaration node) { |
| IMethodBinding binding = node.resolveBinding(); |
| if (binding == null) { |
| return true; |
| } |
| // return value |
| if (binding.getReturnType() != null) { |
| String fqn = binding.getReturnType().getQualifiedName(); |
| if (!"void".equals(fqn)) { |
| recordTypeBinding(binding.getReturnType()); |
| if (!Modifier.isPrivate(node.getModifiers())) { |
| IPackageBinding packageBinding = getPackageBinding(binding.getReturnType()); |
| if (packageBinding != null) { |
| this.partialManifest.recordUsesPackage(this.typePackage, packageBinding.getName()); |
| } |
| } |
| } |
| } |
| // throws clause |
| if (binding.getExceptionTypes() != null) { |
| for (ITypeBinding exceptionBinding : binding.getExceptionTypes()) { |
| recordTypeBinding(exceptionBinding); |
| if (!Modifier.isPrivate(node.getModifiers())) { |
| IPackageBinding packageBinding = getPackageBinding(exceptionBinding); |
| if (packageBinding != null) { |
| this.partialManifest.recordUsesPackage(this.typePackage, packageBinding.getName()); |
| } |
| } |
| } |
| } |
| // parameter |
| if (binding.getParameterTypes() != null) { |
| for (ITypeBinding parameterBinding : binding.getParameterTypes()) { |
| recordTypeBinding(parameterBinding); |
| if (!Modifier.isPrivate(node.getModifiers())) { |
| IPackageBinding packageBinding = getPackageBinding(parameterBinding); |
| if (packageBinding != null) { |
| this.partialManifest.recordUsesPackage(this.typePackage, packageBinding.getName()); |
| } |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(MethodInvocation node) { |
| IMethodBinding binding = node.resolveMethodBinding(); |
| if (binding != null) { |
| recordTypeBinding(binding.getDeclaringClass()); |
| |
| // return value |
| if (binding.getReturnType() != null) { |
| recordTypeBinding(binding.getReturnType()); |
| } |
| // parameter |
| if (binding.getParameterTypes() != null) { |
| for (ITypeBinding parameterBinding : binding.getParameterTypes()) { |
| recordTypeBinding(parameterBinding); |
| } |
| } |
| } else { |
| |
| } |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(NormalAnnotation node) { |
| ITypeBinding binding = node.resolveTypeBinding(); |
| recordTypeBinding(binding); |
| this.partialManifest.recordUsesPackage(this.typePackage, getPackageBinding(binding).getName()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(QualifiedName node) { |
| IBinding binding = node.resolveBinding(); |
| |
| if (isValid(binding)) { |
| // Record the declared type. |
| recordTypeBinding(node.resolveTypeBinding()); |
| |
| // Record the declaring type. |
| if (binding.getKind() == IBinding.VARIABLE) { |
| recordTypeBinding(((IVariableBinding) binding).getDeclaringClass()); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(SimpleName node) { |
| String fqn = node.getFullyQualifiedName(); |
| recordFullyQualifiedName(fqn); |
| return true; |
| } |
| |
| /** |
| * It is important to record the type binding only if it is not recovered. Recovered bindings occur for invalid |
| * source code and incomplete class paths. |
| * |
| * @param binding the binding which may be recorded |
| * @return <code>true</code> if and only if the binding should be recorded |
| */ |
| private boolean isValid(IBinding binding) { |
| return binding != null && !binding.isRecovered(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(SimpleType node) { |
| if (isValid(node.resolveBinding())) { |
| recordTypeBinding(node.resolveBinding()); |
| } else { |
| Name simpleName = node.getName(); |
| String fqn = simpleName.getFullyQualifiedName(); |
| recordFullyQualifiedName(fqn); |
| } |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(SingleMemberAnnotation node) { |
| ITypeBinding binding = node.resolveTypeBinding(); |
| recordTypeBinding(binding); |
| this.partialManifest.recordUsesPackage(this.typePackage, getPackageBinding(binding).getName()); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(TypeDeclaration node) { |
| ITypeBinding binding = node.resolveBinding(); |
| if (binding == null) { |
| return false; |
| } |
| String name = binding.getBinaryName(); |
| this.typePackage = getPackageBinding(binding).getName(); |
| |
| if (!node.isLocalTypeDeclaration()) { |
| this.partialManifest.recordType(name); |
| } |
| |
| for (String referencedType : this.referencedTypes) { |
| recordFullyQualifiedName(referencedType); |
| } |
| |
| if (node.getSuperclassType() != null) { |
| ITypeBinding superClassBinding = node.getSuperclassType().resolveBinding(); |
| recordTypeBinding(superClassBinding); |
| this.partialManifest.recordUsesPackage(this.typePackage, getPackageBinding(superClassBinding).getName()); |
| } |
| |
| for (int i = 0; i < node.superInterfaceTypes().size(); i++) { |
| org.eclipse.jdt.core.dom.Type interfaceType = (org.eclipse.jdt.core.dom.Type) node.superInterfaceTypes().get(i); |
| ITypeBinding interfaceBinding = interfaceType.resolveBinding(); |
| recordTypeBinding(interfaceBinding); |
| this.partialManifest.recordUsesPackage(this.typePackage, getPackageBinding(interfaceBinding).getName()); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(AnonymousClassDeclaration node) { |
| ITypeBinding binding = node.resolveBinding(); |
| if (binding == null) { |
| return false; |
| } |
| this.partialManifest.recordType(binding.getBinaryName()); |
| |
| ITypeBinding superClassBinding = binding.getSuperclass(); |
| if (superClassBinding != null) { |
| recordTypeBinding(superClassBinding); |
| this.partialManifest.recordUsesPackage(this.typePackage, getPackageBinding(superClassBinding).getName()); |
| } |
| |
| ITypeBinding[] interfaceBindings = binding.getInterfaces(); |
| if (interfaceBindings != null) { |
| for (ITypeBinding interfaceBinding : interfaceBindings) { |
| recordTypeBinding(interfaceBinding); |
| this.partialManifest.recordUsesPackage(this.typePackage, getPackageBinding(interfaceBinding).getName()); |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(AnnotationTypeDeclaration node) { |
| ITypeBinding binding = node.resolveBinding(); |
| if (binding == null) { |
| return false; |
| } |
| |
| String name = binding.getBinaryName(); |
| this.typePackage = getPackageBinding(binding).getName(); |
| |
| this.partialManifest.recordType(name); |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean visit(EnumDeclaration node) { |
| ITypeBinding binding = node.resolveBinding(); |
| if (binding == null) { |
| return false; |
| } |
| |
| String name = binding.getBinaryName(); |
| this.typePackage = getPackageBinding(binding).getName(); |
| |
| this.partialManifest.recordType(name); |
| return true; |
| } |
| |
| private IPackageBinding getPackageBinding(ITypeBinding binding) { |
| if (binding != null) { |
| if (binding.isArray()) { |
| return getPackageBinding(binding.getComponentType()); |
| } else { |
| return binding.getPackage(); |
| } |
| } |
| return null; |
| } |
| |
| private String recordFullyQualifiedName(String fqn) { |
| if (!"void".equals(fqn)) { |
| if (this.typePackage == null) { |
| this.referencedTypes.add(fqn); |
| return fqn; |
| } else if (fqn != null && !this.typePackage.equals(getPackageName(fqn))) { |
| // This is required to get FQCNs of the form |
| // org.springframework.util.ReflectionUtils.MethodCallback correctly detected |
| StringTokenizer segments = new StringTokenizer(fqn, "."); |
| if (segments.countTokens() > 1) { |
| List<String> newSegments = new ArrayList<String>(); |
| while (segments.hasMoreTokens()) { |
| String segment = segments.nextToken(); |
| newSegments.add(segment); |
| if (!Character.isLowerCase(segment.charAt(0))) { |
| break; |
| } |
| } |
| fqn = StringUtils.join(newSegments, "."); |
| } |
| |
| this.partialManifest.recordReferencedType(fqn); |
| return fqn; |
| } |
| } |
| return ""; |
| } |
| |
| private String getPackageName(String fullyQualifiedTypeName) { |
| if (fullyQualifiedTypeName == null) { |
| return ""; |
| } |
| int index = fullyQualifiedTypeName.lastIndexOf('.'); |
| |
| if (index > -1) { |
| return fullyQualifiedTypeName.substring(0, index); |
| } |
| |
| return ""; |
| } |
| |
| private String recordTypeBinding(ITypeBinding binding) { |
| if (binding != null) { |
| if (binding.isArray()) { |
| return recordTypeBinding(binding.getComponentType()); |
| } else { |
| String fqn = binding.getBinaryName(); |
| return recordFullyQualifiedName(fqn); |
| } |
| } |
| return ""; |
| } |
| |
| } |