/*******************************************************************************
* Copyright (c) 2010, 2016 Spring IDE Developers
* 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:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.beans.core.metadata.model;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IType;
import org.springframework.asm.ClassReader;
import org.springframework.asm.ClassVisitor;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.metadata.internal.model.BeanMetadataModel;
import org.springframework.ide.eclipse.beans.core.model.IBean;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.ide.eclipse.core.java.annotation.AnnotationMetadataReadingVisitor;
import org.springframework.ide.eclipse.core.java.annotation.IAnnotationMetadata;
import org.springframework.ide.eclipse.core.type.asm.CachingClassReaderFactory;
import org.springframework.ide.eclipse.core.type.asm.ClassReaderFactory;
/**
* Abstract base {@link IBeanMetadataProvider} that uses a {@link AnnotationMetadataReadingVisitor} to load annotation
* meta data from the {@link IBean}'s bean class.
*
* @author Christian Dupuis
* @author Martin Lippert
* @since 2.0.5
*/
public abstract class AbstractAnnotationReadingMetadataProvider extends BeanMetadataProviderAdapter implements
IBeanMetadataProvider {
/**
* Internal cache of {@link ClassReaderFactory} keyed by the corresponding {@link IProject}.
*/
private final Map<IProject, ClassReaderFactory> classReaderFactoryCache = new ConcurrentHashMap<IProject, ClassReaderFactory>();
/**
* Internal cache of {@link IAnnotationMetadata} keyed by the corresponding {@link IType}. It is important to key
* with {@link IType} and not just with FQCN as a class can exist multiple times with the same name in different
* projects.
*/
private final Map<IType, IAnnotationMetadata> metadataCache = new ConcurrentHashMap<IType, IAnnotationMetadata>();
@Override
public final Set<IBeanMetadata> provideBeanMetadata(IBean bean, IBeansConfig beansConfig,
IProgressMonitor progressMonitor) {
long start = System.currentTimeMillis();
Set<IBeanMetadata> beanMetadata = new HashSet<IBeanMetadata>();
IType type = JdtUtils.getJavaType(bean.getElementResource().getProject(), BeansModelUtils.getBeanClass(bean,
null));
// Get annotation meta data
IAnnotationMetadata visitor = getAnnotationMetadata(bean, beansConfig.getElementResource().getProject(), type);
if (visitor != null) {
// Call the actual processing of the found annotations
processFoundAnnotations(bean, beanMetadata, type, visitor, progressMonitor);
}
if (BeanMetadataModel.DEBUG) {
System.out.println("Processing bean [" + bean + "] took " + (System.currentTimeMillis() - start) + "ms");
}
return beanMetadata;
}
/**
* Returns an {@link IAnnotationMetadata} implementation. This method checks the {@link #metadataCache} before
* creating a new instance.
*/
protected IAnnotationMetadata getAnnotationMetadata(IBean bean, IProject project, IType type) {
IType orginalType = type;
// No support for binary types (class files)
if (type == null || type.isBinary()) {
return null;
}
// Check cache first
if (metadataCache.containsKey(orginalType)) {
return metadataCache.get(orginalType);
}
IAnnotationMetadata visitor = null;
// JDT in Eclipse 3.4 supports annotation in the core model
// if (SpringCoreUtils.isEclipseSameOrNewer(3, 4)) {
// visitor = new JdtBasedAnnotationMetadata(orginalType);
// }
// else {
// Get the class reader as late as possible
ClassReaderFactory classReaderFactory = getClassReaderFactory(project);
ClassLoader classLoader = JdtUtils.getClassLoader(project, null);
IProject beansProject = bean.getElementResource().getProject();
// Create new annotation meta data
AnnotationMetadataReadingVisitor annotationVisitor = createAnnotationMetadataReadingVisitor();
visitor = annotationVisitor;
runAnnotationMetadataVisitor(type, classReaderFactory, classLoader, beansProject, annotationVisitor);
// }
// cache here in case exception was thrown we don't want to retry over and over again
if (visitor != null) {
// make sure to cache with the original type
metadataCache.put(orginalType, visitor);
}
return visitor;
}
public void runAnnotationMetadataVisitor(IType type, ClassReaderFactory classReaderFactory, ClassLoader classLoader,
IProject beansProject, AnnotationMetadataReadingVisitor annotationVisitor) {
String className = type.getFullyQualifiedName();
try {
while (className != null && !Object.class.getName().equals(className) && type != null
&& !type.isBinary()) {
ClassReader classReader = classReaderFactory.getClassReader(className);
annotationVisitor.setType(type);
annotationVisitor.setClassloader(classLoader);
classReader.accept((ClassVisitor) annotationVisitor, 0);
className = annotationVisitor.getSuperClassName();
type = JdtUtils.getJavaType(beansProject, className);
}
}
catch (IOException e) {
IStatus status = new Status(IStatus.WARNING, BeansCorePlugin.PLUGIN_ID, 0, "Error during AST class visiting", e);
BeansCorePlugin.log(status);
}
}
/**
* Returns a {@link ClassReaderFactory} for the given <code>project</code>.
* <p>
* This method checks for an already created {@link ClassReaderFactory} in the internal cache
* {@link #classReaderFactoryCache} before creating a new instance.
*/
private ClassReaderFactory getClassReaderFactory(IProject project) {
if (!classReaderFactoryCache.containsKey(project)) {
classReaderFactoryCache.put(project, new CachingClassReaderFactory(JdtUtils.getClassLoader(project,
null)));
}
return classReaderFactoryCache.get(project);
}
/**
* Creates a new {@link AnnotationMetadataReadingVisitor} instances.
* <p>
* Note: subclasses may override this method to provide another implementation.
*/
protected AnnotationMetadataReadingVisitor createAnnotationMetadataReadingVisitor() {
return new AnnotationMetadataReadingVisitor();
}
/**
* Method to be implemented by sub classes to process found annotation on {@link IBean} classes.
* @param bean the current {@link IBean}
* @param beanMetaDataSet the {@link Set} of {@link IBeanMetadata} to add the new meta data to
* @param type the current {@link IType} that was loaded from the bean's class name
* @param progressMonitor the progress monitor to report status
* @param visitor the annotation visitor
*/
protected abstract void processFoundAnnotations(IBean bean, Set<IBeanMetadata> beanMetaDataSet, IType type,
IAnnotationMetadata metadata, IProgressMonitor progressMonitor);
}