/*
* Copyright (c) 2010-2012 Research In Motion Limited. All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License, Version 1.0,
* which accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package net.rim.ejde.internal.validation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import net.rim.ejde.internal.core.IRIMMarker;
import net.rim.ejde.internal.model.BlackBerryProjectCoreNature;
import net.rim.ejde.internal.util.ProjectUtils;
import net.rim.ejde.internal.util.ResourceBuilderUtils;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
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.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.jface.text.BadLocationException;
/**
* Singleton class that controls validation for all BB projects
*
* Listens for resource changes and triggers validation when any occur
*
* @author bchabot
*/
public class ValidationManager {
private static final Logger _logger = Logger.getLogger( ValidationManager.class );
private static final int NO_SEVERITY = -1;
private static ValidationManager _instance;
/** Map of IProject to ProjectValidationManager */
private Map< IProject, ProjectValidationManager > _projectModelValidators;
private DiagnosticManager _diagnosticManager;
/**
* private constructor, cannot be instantiated
*/
private ValidationManager() {
_projectModelValidators = new Hashtable< IProject, ProjectValidationManager >();
_diagnosticManager = new DiagnosticManager();
_diagnosticManager.addChangeListener( new MarkerSynchronizer() );
ResourcesPlugin.getWorkspace().addResourceChangeListener( new MyResourceChangeListener(),
IResourceChangeEvent.POST_CHANGE );
}
/**
* Returns the singleton instance.
*
* @return <code>ValidationManager</code>
*/
public static ValidationManager getInstance() {
if( _instance == null ) {
_instance = new ValidationManager();
}
return _instance;
}
/**
* Removes diagnostics for given deleted model object
*
* @param iproject
* @param iProject
* @param modelObject
*/
public void handleDeletedResource( IProject iproject, IProject iProject, Object modelObject ) {
getProjectValidator( iproject ).removeAllDiagnostics( iProject, modelObject );
}
/**
* Clean-up for closed projects
*
* @param closedProjects
*/
public void handleClosedProjects( Collection< IProject > closedProjects ) {
synchronized( _projectModelValidators ) {
for( IProject iproject : closedProjects ) {
ProjectValidationManager projValidator = _projectModelValidators.remove( iproject );
if( projValidator != null ) {
getDiagnosticManager().removeProjectRegistry( projValidator.getIProject() );
}
}
}
}
private ProjectValidationManager getProjectValidator( IProject iproject ) {
synchronized( _projectModelValidators ) {
ProjectValidationManager projectValidator = _projectModelValidators.get( iproject );
if( projectValidator == null ) {
projectValidator = new ProjectValidationManager( getDiagnosticManager(), iproject );
_projectModelValidators.put( iproject, projectValidator );
}
return projectValidator;
}
}
/**
* Validates the given project.
*
* @param iproject
* The project to be validated
* @param monitor
* The progress monitor or null
*/
public void validateProject( IProject iproject, final IProgressMonitor monitor ) {
final ProjectValidationManager projValidator = getProjectValidator( iproject );
if( projValidator != null ) {
projValidator.validateProject( monitor );
}
}
/**
* Validates the given projectgs.
*
* @param changedProjects
* @param monitor
*/
public void validateProjects( Collection< IProject > changedProjects, IProgressMonitor monitor ) {
if( monitor == null ) {
monitor = new NullProgressMonitor();
}
monitor.beginTask( "Validating projects", changedProjects.size() );
for( IProject iproject : changedProjects ) {
// do not validate non-BB project
try {
if( !iproject.hasNature( BlackBerryProjectCoreNature.NATURE_ID ) ) {
continue;
}
} catch( CoreException e ) {
_logger.error( e.getMessage() );
continue;
}
String msg = "Validating project " + iproject.getName();
_logger.debug( msg );
monitor.subTask( msg );
validateProject( iproject, monitor );
monitor.worked( 1 );
}
monitor.done();
}
/**
* Retrieves the DiagnosticManager
*
* @return
*/
private DiagnosticManager getDiagnosticManager() {
return _diagnosticManager;
}
/**
* Cleans the respective target diags from the diagnosticManager registry
*
* @param project
* @param target
*/
public void cleanObjectDiags( IProject project, Object target ) {
_diagnosticManager.cleanObjectDiags( project, target );
}
/**
* Determine if given project has any validation errors
*
* @param refreshValidation
* - true if validation should be rerun for project before returning result
* @param iRes
* @return true if project has one or more errors, false otherwise
*/
public boolean hasValidationErrors( boolean refreshValidation, IResource iRes, IProgressMonitor monitor ) {
if( refreshValidation ) {
if( iRes instanceof IProject ) {
validateProject( (IProject) iRes, monitor );
}
}
try {
if( iRes.findMaxProblemSeverity( IRIMMarker.MODEL_PROBLEM, true, IResource.DEPTH_INFINITE ) == IMarker.SEVERITY_ERROR ) {
return true;
}
return false;
} catch( CoreException e ) {
_logger.error( e );
return true;
}
}
/**
* Listens to BBDiagnostic change events, and creates, deletes markers for them
*
* @author bchabot
*
*/
private class MarkerSynchronizer implements IDiagnosticManagerChangeListener {
public MarkerSynchronizer() {
}
public void addedDiagnostic( IProject project, Object target, BBDiagnostic diagnostic ) {
IResource ires = getIResource( target );
if( ires != null ) {
try {
clearValidationMarkers( ires, IResource.DEPTH_ZERO );
} catch( CoreException e ) {
_logger.error( e.getMessage() );
}
for( Diagnostic child : diagnostic.getChildren() ) {
if( child.getSeverity() != Diagnostic.OK ) {
createValidationMarker( ires, child.getMessage(), child.getCode(),
getMarkerSeverity( child.getSeverity() ) );
}
}
}
}
public void removedDiagnostic( IProject project, Object target ) {
try {
IResource ires = getIResource( target );
if( ires != null ) {
clearValidationMarkers( ires, IResource.DEPTH_ZERO );
}
} catch( CoreException e ) {
_logger.error( e );
}
}
public void removedProject( IProject project ) {
// eclipse should delete markers when project deleted, not needed
}
private IResource getIResource( Object target ) {
if( ( target instanceof IProject ) || ( target instanceof IFile ) ) {
return (IResource) target;
}
return null;
}
}
private static int getMarkerSeverity( int diagSeverity ) {
int markerSeverity = NO_SEVERITY;
switch( diagSeverity ) {
case Diagnostic.ERROR:
markerSeverity = IMarker.SEVERITY_ERROR;
break;
case Diagnostic.WARNING:
markerSeverity = IMarker.SEVERITY_WARNING;
break;
case Diagnostic.INFO:
markerSeverity = IMarker.SEVERITY_INFO;
break;
}
return markerSeverity;
}
/**
* Returns if the given resource has any problem.
*
* @param resource
* The given resource
* @return true if yes; otherwise false
*/
public static boolean hasProblems( IResource resource ) {
if( !resource.isAccessible() ) {
return false;
}
try {
return ( resource.findMarkers( IRIMMarker.MODEL_PROBLEM, true, IResource.DEPTH_ZERO ).length > 0 );
} catch( CoreException e ) {
_logger.error( e );
}
return false;
}
/**
* Find if resource has any general problems of the specific id
*
* @param resource
* @param id
* @return
*/
public boolean hasProblems( IResource resource, int id ) {
return hasProblems( resource, IRIMMarker.MODEL_PROBLEM, id );
}
/**
* Find if resource has any problems of the specific type and id
*
* @param resource
* @param type
* @param id
* @return
*/
public boolean hasProblems( IResource resource, String type, int id ) {
if( !resource.isAccessible() ) {
return false;
}
try {
IMarker[] markers = resource.findMarkers( type, true, IResource.DEPTH_ZERO );
for( IMarker marker : markers ) {
if( marker.getAttribute( IRIMMarker.ID, -1 ) == id ) {
return true;
}
}
} catch( CoreException e ) {
_logger.error( e );
}
return false;
}
/**
* Create a new marker in the specified resource.
*
* @param resource
* @param message
* @param lineNumber
* @param severity
* @throws CoreException
* @throws BadLocationException
*/
private static void createValidationMarker( IResource resource, String message, int lineNumber, int severity ) {
try {
ResourceBuilderUtils.createProblemMarker( resource, IRIMMarker.MODEL_PROBLEM, message, lineNumber, severity );
} catch( CoreException e ) {
_logger.error( e );
}
}
/**
* Clears previous preprocessor markers from the specified resource.
*
* @param resource
* @param depth
*
* @throws CoreException
*/
private static void clearValidationMarkers( IResource resource, int depth ) throws CoreException {
ResourceBuilderUtils.cleanProblemMarkers( resource, new String[] { IRIMMarker.MODEL_PROBLEM }, depth );
}
class MyResourceChangeListener implements IResourceChangeListener {
@Override
public void resourceChanged( IResourceChangeEvent event ) {
if( event.getType() == IResourceChangeEvent.POST_CHANGE ) {
IResourceDelta rootDelta = event.getDelta();
ResDeltaVisitor visitor = new ResDeltaVisitor();
try {
rootDelta.accept( visitor );
} catch( CoreException e ) {
_logger.error( "", e );
}
Set< IProject > allChangedProjects = ProjectUtils.getAllReferencingProjects( visitor.changedProjects
.toArray( new IProject[ visitor.changedProjects.size() ] ) );
validateProjects( allChangedProjects, new NullProgressMonitor() );
handleClosedProjects( visitor.closedOrDeletedProjects );
}
}
}
/**
* The Class ResDeltaVisitor.
*/
private static class ResDeltaVisitor implements IResourceDeltaVisitor {
public Collection< IProject > changedProjects;
public Collection< IProject > closedOrDeletedProjects;
/**
* Instantiates a new res delta visitor.
*/
public ResDeltaVisitor() {
super();
changedProjects = new ArrayList< IProject >();
closedOrDeletedProjects = new ArrayList< IProject >();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
*/
public boolean visit( IResourceDelta delta ) {
IResource iresource = delta.getResource();
if( iresource.isDerived() ) {
return false;
}
IProject iproject = iresource.getProject();
if( iproject != null && ( !iproject.isOpen() || !iproject.exists() ) ) {
closedOrDeletedProjects.add( iproject );
return false;
}
if( delta.getKind() == IResourceDelta.CHANGED ) {
// only react to content changes, otherwise this could just simply be a marker change, etc
if( ( delta.getFlags() & IResourceDelta.CONTENT ) != 0 && ValidationUtils.needToBeValidated( iresource )
&& !changedProjects.contains( iresource.getProject() ) ) {
changedProjects.add( iresource.getProject() );
}
return true; // visit the children
} else if( delta.getKind() == IResourceDelta.ADDED && !changedProjects.contains( iresource.getProject() ) ) {
changedProjects.add( iresource.getProject() );
return false;
} else if( delta.getKind() == IResourceDelta.REMOVED && !changedProjects.contains( iresource.getProject() ) ) {
changedProjects.add( iresource.getProject() );
return false;
}
return true;
}
}
}