/******************************************************************************* * Copyright (c) 2005, 2015 BEA Systems, 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: * mkaufman@bea.com - initial API and implementation * *******************************************************************************/ package org.eclipse.jdt.apt.core.internal.generatedfile; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.apt.core.internal.AptPlugin; import org.eclipse.jdt.apt.core.internal.AptProject; import org.eclipse.jdt.apt.core.util.AptConfig; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; /** * A jdt.core pre-process resource change listener that manages generated resources. * <p> * * Note that this is both a pre-build listener and a post-change listener, * because there is a bug in the resource change event notification in the platform: * sometimes they fail to send out deletion notifications for files in pre-build, * but they do send them out in post-change. */ public class GeneratedResourceChangeListener implements IResourceChangeListener { // Synchronized collection, as post-change notifications could come in // simultaneously. Note that pre-build will not though, as it holds the // workspace lock private final Set<IResource> deletedResources = Collections.synchronizedSet(new HashSet<IResource>()); public GeneratedResourceChangeListener(){} public void resourceChanged(IResourceChangeEvent event) { if ( event.getType() == IResourceChangeEvent.PRE_CLOSE ) { IProject p = (IProject)event.getResource(); if( AptPlugin.DEBUG_GFM ) AptPlugin.trace( "generated resource change listener got a pre-close event: project = " + p.getName()); //$NON-NLS-1$ IJavaProject jp = JavaCore.create(p); AptPlugin.getAptProject(jp).projectClosed(); } else if ( event.getType() == IResourceChangeEvent.PRE_DELETE ) { // TODO: need to update projectDeleted() to delete the generated_src folder // in an async thread. The resource tree is locked here. IProject p = (IProject)event.getResource(); if( AptPlugin.DEBUG_GFM ) AptPlugin.trace( "generated resource change listener got a pre-delete event: project = " + p.getName()); //$NON-NLS-1$ IJavaProject jp = JavaCore.create(p); AptPlugin.getAptProject(jp).projectDeleted(); AptPlugin.deleteAptProject(jp); } else if ( event.getType() == IResourceChangeEvent.PRE_BUILD ) { try { if( AptPlugin.DEBUG_GFM ) AptPlugin.trace("generated resource change listener got a pre-build event"); //$NON-NLS-1$ final PreBuildVisitor pbv = new PreBuildVisitor(); // First we need to handle previously deleted resources (from the post-change event), // because we could not perform file i/o during that event for (IResource resource : deletedResources) { pbv.handleDeletion(resource); } event.getDelta().accept( pbv ); addGeneratedSrcFolderTo(pbv.getProjectsThatNeedGenSrcFolder()); // Now clear the set of deleted resources, // as we don't want to re-handle them deletedResources.clear(); } catch ( CoreException ce ) { AptPlugin.log(ce, "Error during pre-build resource change"); //$NON-NLS-1$ } } else if (event.getType() == IResourceChangeEvent.POST_CHANGE) { if( AptPlugin.DEBUG_GFM ) AptPlugin.trace( "generated resource change listener got a post-change event"); //$NON-NLS-1$ PostChangeVisitor pcv = new PostChangeVisitor(); try { event.getDelta().accept(pcv); } catch (CoreException ce) { AptPlugin.log(ce, "Error during post-change resource event"); //$NON-NLS-1$ } } } private void addGeneratedSrcFolderTo(final Set<IProject> projs ){ for(IProject proj : projs ){ final IJavaProject javaProj = JavaCore.create(proj); if(javaProj.getProject().isOpen() && AptConfig.isEnabled(javaProj)){ final GeneratedSourceFolderManager gsfm = AptPlugin.getAptProject(javaProj).getGeneratedSourceFolderManager(); gsfm.ensureFolderExists(); } } } /** * We need a post-change visitor, as there is a bug in the platform for * resource change notification -- some items will be reported *only* in the post-change event, * so we keep track of them here and handle them in the pre-build */ private class PostChangeVisitor implements IResourceDeltaVisitor { public boolean visit(IResourceDelta delta) throws CoreException { if( delta.getKind() == IResourceDelta.REMOVED ){ if (AptPlugin.DEBUG_GFM) { AptPlugin.trace("generated resource post-change listener adding to deletedResources:" + //$NON-NLS-1$ delta.getResource().getName()); } deletedResources.add(delta.getResource()); } return true; } } private class PreBuildVisitor implements IResourceDeltaVisitor { // projects that we need to add the generated source folder to. private final Set<IProject> _addGenFolderTo = new HashSet<>(); // any projects that is closed or about to be deleted private final Set<IProject> _removedProjects = new HashSet<>(); public boolean visit(IResourceDelta delta) throws CoreException { IResource r = delta.getResource(); IProject project = r.getProject(); if ( project == null ) return true; if( delta.getKind() == IResourceDelta.REMOVED ){ if (!deletedResources.contains(r)) { handleDeletion(r); } } else if( r instanceof IProject ){ final IProject proj = (IProject)delta.getResource(); if( canUpdate(proj) ){ _addGenFolderTo.add(proj); } else _removedProjects.add(proj); } return true; } private void handleDeletion(IResource resource) throws CoreException { if (AptPlugin.DEBUG_GFM) { AptPlugin.trace("handleDeletion: resource = " + resource.getName()); //$NON-NLS-1$ } IProject project = resource.getProject(); final IJavaProject javaProj = JavaCore.create(project); final AptProject aptProj = AptPlugin.getAptProject(javaProj); if( resource instanceof IFile ){ final GeneratedFileManager gfm = aptProj.getGeneratedFileManager(); IFile f = (IFile)resource; gfm.fileDeleted(f); } else if( resource instanceof IFolder ){ final GeneratedSourceFolderManager gsfm = aptProj.getGeneratedSourceFolderManager(); IFolder f = (IFolder) resource; if ( gsfm.isGeneratedSourceFolder( f ) ){ gsfm.folderDeleted(); // all deletion occurs before any add (adding the generated source directory) if( !_removedProjects.contains(project) ){ _addGenFolderTo.add(project); } // if the project is already closed or in the process of being // deleted, will ignore this deletion since we cannot correct // the classpath anyways. } } else if( resource instanceof IProject ){ _removedProjects.add((IProject)resource); } } Set<IProject> getProjectsThatNeedGenSrcFolder(){ _addGenFolderTo.removeAll(_removedProjects); return _addGenFolderTo; } private boolean canUpdate(IProject proj) throws CoreException { return proj.isOpen() && proj.exists() && proj.hasNature(JavaCore.NATURE_ID); } } }