/*******************************************************************************
* Copyright (c) 2012 VMware, Inc.
* 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:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.maven.internal.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.ide.eclipse.core.project.IProjectBuilder;
import org.springframework.ide.eclipse.maven.MavenCorePlugin;
/**
* {@link IProjectBuilder} that overcomes some limitations of the DataNucleus Enhancer.
* <p>
* This implementation will check if any of the incremental changes require a re-enhancing.
* @author Christian Dupuis
* @since 2.5.0
*/
@SuppressWarnings("restriction")
public class DataNucleusEnhancerProjectBuilder implements IProjectBuilder, InitializingBean {
private static final String ENHANCER_JOB_CLASS_NAME = "org.datanucleus.ide.eclipse.jobs.EnhancerJob";
private static final char[] ENTITY_BINARY_CLASS_NAME = "Ljavax/persistence/Entity;".toCharArray();
private static final char[][] ENHANCED_BINARY_CLASS_NAMES = { "javax/jdo/spi/Detachable".toCharArray(),
"javax/jdo/spi/PersistenceCapable".toCharArray() };
private static boolean IS_DATANUCLEUS_PRESENT = isPresent();
private static boolean isPresent() {
try {
Class.forName(ENHANCER_JOB_CLASS_NAME);
return true;
}
catch (ClassNotFoundException e) {
return false;
}
}
/**
* {@inheritDoc}
*/
public void build(Set<IResource> affectedResources, int kind, IProgressMonitor monitor) throws CoreException {
final Set<IJavaProject> javaProjects = new HashSet<IJavaProject>();
for (IResource resource : affectedResources) {
IJavaProject javaProject = JdtUtils.getJavaProject(resource);
if (javaProject != null) {
javaProjects.add(javaProject);
}
}
for (IJavaProject javaProject : javaProjects) {
try {
// Reflectively load the DataNucleus project builder to prevent compile time dependency
Class<?> enhancerJobClass = MavenCorePlugin.getDefault().getBundle().loadClass(ENHANCER_JOB_CLASS_NAME);
Job enhancerJob = (Job) enhancerJobClass.getConstructor(IJavaProject.class).newInstance(javaProject);
enhancerJob.setPriority(Job.SHORT);
enhancerJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
enhancerJob.addJobChangeListener(new JobChangeAdapter() {
public void done(IJobChangeEvent event) {
Job refreshJob = new Job("Update Resource Tree after enhance") {
@Override
protected IStatus run(IProgressMonitor monitor) {
for (IJavaProject javaProject : javaProjects) {
try {
javaProject.getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
catch (CoreException e) {
}
}
return Status.OK_STATUS;
}
};;
refreshJob.setPriority(Job.SHORT);
refreshJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
refreshJob.setSystem(true);
refreshJob.schedule();
}
});
enhancerJob.schedule();
}
catch (Exception e) {
}
}
}
/**
* {@inheritDoc}
*/
public void cleanup(IResource resource, IProgressMonitor monitor) throws CoreException {
// Nothing to clean up here
}
/**
* {@inheritDoc}
*/
public Set<IResource> getAffectedResources(IResource resource, int kind, int deltaKind) throws CoreException {
if (IS_DATANUCLEUS_PRESENT) {
if (kind == IncrementalProjectBuilder.FULL_BUILD) {
return Collections.singleton((IResource) resource.getProject());
}
else if (resource instanceof IFile && resource.getName().endsWith(".class") && resource.isAccessible()
&& resource.isSynchronized(IResource.DEPTH_ZERO)) {
// Check if the class has the @Entity annotation
InputStream is = ((IFile) resource).getContents();
try {
ClassFileReader classFileReader = ClassFileReader.read(is, resource.getName());
IBinaryAnnotation[] annotations = classFileReader.getAnnotations();
if (annotations != null) {
for (IBinaryAnnotation annotation : annotations) {
if (CharOperation.equals(ENTITY_BINARY_CLASS_NAME, annotation.getTypeName())) {
if (classFileReader.getInterfaceNames() != null) {
for (char[] interfaceName : classFileReader.getInterfaceNames()) {
for (char[] enhancedInterfaceName : ENHANCED_BINARY_CLASS_NAMES) {
if (CharOperation.equals(interfaceName, enhancedInterfaceName)) {
return Collections.emptySet();
}
}
}
}
return Collections.singleton(resource);
}
}
}
}
catch (ClassFormatException e) {
// TODO CD add error handling
}
catch (IOException e) {
// TODO CD add error handling
}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException e) {
// TODO CD add error handling
}
}
}
}
}
return Collections.emptySet();
}
/**
* {@inheritDoc}
*/
public void afterPropertiesSet() throws Exception {
if (!IS_DATANUCLEUS_PRESENT) {
throw new BeanCreationException("DataNucleus not available");
}
}
}