/*
* 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.core;
import java.util.List;
import java.util.TreeMap;
import net.rim.ejde.internal.builders.ResourceBuilder;
import net.rim.ejde.internal.model.BlackBerryProjectCoreNature;
import net.rim.ejde.internal.model.BlackBerryProperties;
import net.rim.ejde.internal.packaging.PackagingManager;
import net.rim.ejde.internal.util.ImportUtils;
import net.rim.ejde.internal.util.Messages;
import net.rim.ejde.internal.util.ProjectUtils;
import net.rim.ejde.internal.util.ResourceBuilderUtils;
import net.rim.ejde.internal.validation.ValidationManager;
import net.rim.ide.Project;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
abstract public class BasicClasspathElementChangedListener implements IElementChangedListener {
static private Logger _log = Logger.getLogger( BasicClasspathElementChangedListener.class );
private TreeMap< String, Boolean > _changedBuildClasspath = new TreeMap< String, Boolean >();
public BasicClasspathElementChangedListener() {
}
public void elementChanged( ElementChangedEvent event ) {
IJavaElementDelta[] children = event.getDelta().getChangedChildren(); // children = IProjects
for( IJavaElementDelta child : children ) {
IProject project = child.getElement().getJavaProject().getProject();
int size = child.getAffectedChildren().length; // .getChangedElement() = JavaProject
if( size == 1 ) {
IJavaElementDelta elementDelta = child.getAffectedChildren()[ 0 ]; // if it is only 1, name is ".tmp"
// and elementDelta.kind = 4
// (CHANGED)
IJavaElement changedElement = elementDelta.getElement();
if( changedElement.getElementName().equals(
ImportUtils.getImportPref( ResourceBuilder.LOCALE_INTERFACES_FOLDER_NAME ) ) ) {
_changedBuildClasspath.put( project.getName(), Boolean.FALSE );
break;
}
}
if( isClasspathChange( child ) ) {// adding classpath entries might induce reordering the classpath entries
_changedBuildClasspath.put( project.getName(), Boolean.TRUE );
// notify the listeners
EJDEEventNotifier.getInstance()
.notifyClassPathChanged( child.getElement().getJavaProject(), hasCPRemoved( child ) );
// validate the project
ValidationManager.getInstance()
.validateProjects(
ProjectUtils.getAllReferencingProjects( new IProject[] { child.getElement().getJavaProject()
.getProject() } ), null );
}
if( ( child.getFlags() & IJavaElementDelta.F_CLASSPATH_CHANGED ) != 0
|| ( child.getFlags() & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED ) != 0 ) {
IJavaElement javaElement = child.getElement();
final IJavaProject javaProject = javaElement.getJavaProject();
classPathChanged( javaProject, child );
}
checkSourceAttachement( child.getAffectedChildren() );
}
for( final IJavaElementDelta addedElemDelta : event.getDelta().getAddedChildren() ) {
final IJavaProject javaProject = addedElemDelta.getElement().getJavaProject();
try {
if( javaProject.getProject().hasNature( BlackBerryProjectCoreNature.NATURE_ID ) ) {
if( addedElemDelta.getAffectedChildren().length == 0 ) {
final IJavaElement addedElement = addedElemDelta.getElement();
if( addedElement instanceof IJavaProject ) {
final IJavaProject addedJavaProj = (IJavaProject) addedElement;
if( addedJavaProj.equals( javaProject ) ) {
projectCreated( javaProject );
}
}
}
}
} catch( final CoreException ce ) {
_log.error( "", ce );
}
}
}
public void classPathChanged( final IJavaProject javaProj, final IJavaElementDelta childDelta ) {
_log.trace( "Entered classPathChanged(); project: " + javaProj.getProject().getName() );
// check project options
checkProjectOptions( javaProj );
// check project dependency
hasProjectDependencyProblem( javaProj );
}
private void checkProjectOptions( IJavaProject javaProj ) {
_log.trace( "Entered checkProjectOptions(); project: " + javaProj.getProject().getName() );
try {
// we only check BB projects and Java projects which are depended by any BB projects
if( !javaProj.getProject().hasNature( BlackBerryProjectCoreNature.NATURE_ID )
&& !ProjectUtils.isDependedByBBProject( javaProj.getProject() ) )
return;
// get all referred projects
List< IJavaProject > referedProjects = ProjectUtils.getAllReferencedJavaProjects( javaProj );
List< IJavaProject > nonBBProjects = ProjectUtils.getNonBBJavaProjects( referedProjects );
// get non-bb projects which has JDK compatibility problem
List< IJavaProject > projectsWithProblem = ProjectUtils
.getJavaProjectsContainJDKCompatibilityProblem( referedProjects );
if( nonBBProjects.size() > 0 ) {
setJDKCompatibilitySettings( javaProj, nonBBProjects, projectsWithProblem );
}
} catch( JavaModelException e ) {
_log.error( e.getMessage() );
} catch( CoreException e ) {
_log.error( e.getMessage() );
}
}
/**
* The flags indicate a classpath chhange and its type
*
* @param flags
* the flags to inspect
* @return true if the flag flags a classpath change
*/
static public boolean isClasspathChangeFlag( int flags ) {
if( ( flags & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED ) != 0 )
return true;
if( ( flags & IJavaElementDelta.F_ADDED_TO_CLASSPATH ) != 0 )
return true;
if( ( flags & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH ) != 0 )
return true;
if( ( flags & IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED ) != 0 )
return true;
return false;
}
private boolean hasCPRemoved( IJavaElementDelta delta ) {
for( IJavaElementDelta cpDelta : delta.getAffectedChildren() ) {
if( ( cpDelta.getFlags() & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH ) != 0
&& cpDelta.getElement().getElementType() == IJavaElement.PACKAGE_FRAGMENT_ROOT
&& ( (IPackageFragmentRoot) cpDelta.getElement() ).getPath().lastSegment().equals( IConstants.RIM_API_JAR ) )
return true;
}
return false;
}
private void setJDKCompatibilitySettings( final IJavaProject mainProject, final List< IJavaProject > nonBBProjects,
final List< IJavaProject > projectsWithProblem ) {
_log.trace( "Entered setJDKCompatibilitySettings()" );
if( projectsWithProblem.size() > 0 ) {
StringBuffer projectList = new StringBuffer();
for( IJavaProject javaProject : projectsWithProblem ) {
projectList.append( javaProject.getProject().getName() + "\n" );
}
final String projectNames = projectList.toString();
Display.getDefault().syncExec( new Runnable() {
public void run() {
Shell shell = ContextManager.getActiveWorkbenchWindow().getShell();
StringBuffer messageBuffer = new StringBuffer( Messages.ClasspathChangeManager_settingJDKComplianceMsg1 );
messageBuffer.append( "\n" );
messageBuffer.append( NLS.bind( Messages.ClasspathChangeManager_settingJDKComplianceMsg2, mainProject
.getProject().getName() ) );
messageBuffer.append( "\n" );
messageBuffer.append( projectNames );
messageBuffer.append( "\n" );
messageBuffer.append( Messages.ClasspathChangeManager_settingJDKComplianceMsg3 );
MessageDialog complianceDialog = new MessageDialog( shell, Messages.ClasspathChangeManager_DialogTitle, null,
messageBuffer.toString(), MessageDialog.INFORMATION, new String[] {
Messages.IConstants_OK_BUTTON_TITLE, Messages.IConstants_CANCEL_BUTTON_TITLE }, 0 );
int buttonEventCode = complianceDialog.open();
switch( buttonEventCode ) {
case Window.OK: {
processNonBBProjects( nonBBProjects, projectsWithProblem, true );
break;
}
case Window.CANCEL: {
processNonBBProjects( nonBBProjects, projectsWithProblem, false );
break;
}
default:
throw new IllegalArgumentException( "Unsupported dialog button event!" );
}
complianceDialog.close();
}
} );
} else {
processNonBBProjects( nonBBProjects, projectsWithProblem, false );
}
}
private void processNonBBProjects( final List< IJavaProject > nonBBProjects, final List< IJavaProject > projectsWithProblem,
final boolean fix ) {
for( IJavaProject javaProject : nonBBProjects ) {
if( fix && projectsWithProblem.contains( javaProject ) ) {
ImportUtils.initializeProjectOptions( javaProject );
}
}
if( fix ) {
Shell shell = ContextManager.getActiveWorkbenchWindow().getShell();
String message = Messages.ClasspathChangeManager_rebuildProjectMsg1;
StringBuffer projectList = new StringBuffer();
for( IJavaProject javaProject : projectsWithProblem ) {
projectList.append( javaProject.getProject().getName() + "\n" );
}
message += projectList.toString();
message += Messages.ClasspathChangeManager_rebuildProjectMsg2;
MessageDialog dialog = new MessageDialog( shell, Messages.ClasspathChangeManager_RebuildProjectDialogTitle, null,
message, MessageDialog.INFORMATION, new String[] { Messages.IConstants_OK_BUTTON_TITLE,
Messages.IConstants_CANCEL_BUTTON_TITLE }, 0 );
int buttonEventCode = dialog.open();
switch( buttonEventCode ) {
case Window.OK: {
buildProjects( projectsWithProblem );
break;
}
case Window.CANCEL: {
break;
}
default:
throw new IllegalArgumentException( "Unsupported dialog button event!" );
}
dialog.close();
}
}
private void buildProjects( final List< IJavaProject > projects ) {
BuildTask task = new BuildTask( projects );
task.setRule( ResourcesPlugin.getWorkspace().getRoot() );
task.schedule();
}
/**
* This methods determines whether an IProject has its build classpath changed by an explicit user action (from the Java Build
* Path dialog)
*
* @param project
* @return
*/
public boolean hasClasspathChanged( IProject project ) {
Boolean projectChanged = _changedBuildClasspath.get( project.getName() );
if( projectChanged == null || !projectChanged.booleanValue() )
return false;
return true;
}
/**
* This method marks an Eclipse project as not having the class path changed by an explicit user action Its callers indicate
* one build classpath entry has already been processed for a given project
*
* @param project
* the Eclipse Project
*/
public void markUnchangedClasspath( IProject project ) {
_changedBuildClasspath.put( project.getName(), Boolean.FALSE );
}
static public boolean hasProjectDependencyProblem( IJavaProject javaProject ) {
IProject project = javaProject.getProject();
try {
ResourceBuilderUtils.cleanProblemMarkers( project, new String[] { IRIMMarker.PROJECT_DEPENDENCY_PROBLEM_MARKER },
IResource.DEPTH_ONE );
} catch( CoreException e ) {
_log.error( e );
}
IClasspathEntry[] classpathEntries = null;
try {
classpathEntries = javaProject.getRawClasspath();
} catch( JavaModelException e ) {
_log.error( e );
return true;
}
IProject dependentProject = null;
String projectName = null;
boolean hasDependencyError = false;
for( IClasspathEntry entry : classpathEntries ) {
if( entry.getEntryKind() == IClasspathEntry.CPE_PROJECT ) {
projectName = entry.getPath().lastSegment();
dependentProject = ResourcesPlugin.getWorkspace().getRoot().getProject( projectName );
if( !isValidDependency( javaProject.getProject(), dependentProject ) && !hasDependencyError ) {
hasDependencyError = true;
}
}
}
return hasDependencyError;
}
static private boolean isValidDependency( final IProject mainProject, IProject dependentProject ) {
try {
// we allow a BB project depending on any other non-BB projects
if( !dependentProject.hasNature( BlackBerryProjectCoreNature.NATURE_ID ) )
return true;
} catch( CoreException e ) {
_log.error( e.getMessage() );
String message = NLS.bind( Messages.ClasspathChangeManager_PROJECT_NATURE_ERROR, dependentProject.getName() );
try {
ResourceBuilderUtils.createProblemMarker( mainProject, IRIMMarker.PROJECT_DEPENDENCY_PROBLEM_MARKER, message, -1,
IMarker.SEVERITY_ERROR );
} catch( CoreException e1 ) {
_log.error( e1.getMessage() );
}
}
BlackBerryProperties dependentProperties = ContextManager.PLUGIN.getBBProperties( dependentProject.getName(), false );
if( dependentProperties == null )
return false;
if( PackagingManager.getProjectTypeID( dependentProperties._application.getType() ) != Project.LIBRARY ) {
final String message = NLS.bind( Messages.ClasspathChangeManager_WrongProjectDependencyMessage, new String[] {
mainProject.getName(), dependentProject.getName() } );
try {
_log.error( message );
ResourceBuilderUtils.createProblemMarker( mainProject, IRIMMarker.PROJECT_DEPENDENCY_PROBLEM_MARKER, message, -1,
IMarker.SEVERITY_ERROR );
} catch( CoreException e ) {
_log.error( e );
}
return false;
}
return true;
}
private void projectCreated( final IJavaProject javaProj ) {
try {
if( javaProj.getProject().hasNature( BlackBerryProjectCoreNature.NATURE_ID ) ) {
checkSourceFolders( javaProj );
}
} catch( CoreException e ) {
_log.error( "Error Changing Project Output Folder", e );
}
EJDEEventNotifier.getInstance().notifyNewProjectCreated( javaProj );
}
/**
* Checks the source attachment. If the <code>changedElements</code> contains jars/variables which are added to the classpath,
* this method will try to search for the source jar and attach the source jar to the binary jar if it is found.
*
* @param changedElements
*/
abstract protected void checkSourceAttachement( IJavaElementDelta[] changedElements );
/**
* Checks if all source files are existing. If not, create them.
*
* @param javaProj
*/
private void checkSourceFolders( final IJavaProject javaProj ) {
if( javaProj == null )
return;
if( javaProj.exists() ) {
try {
if( !javaProj.isOpen() ) {
javaProj.open( new NullProgressMonitor() );
}
IClasspathEntry[] entries = javaProj.getRawClasspath();
for( IClasspathEntry entry : entries ) {
if( IClasspathEntry.CPE_SOURCE == entry.getEntryKind() ) {
IPath path = entry.getPath();
final IPath folderPath = path.removeFirstSegments( 1 );
if( !folderPath.isEmpty() ) {
Display.getDefault().asyncExec( new Runnable() {
public void run() {
try {
ImportUtils.createFolders( javaProj.getProject(), folderPath, IResource.FORCE );
} catch( CoreException e ) {
_log.error( e.getMessage() );
}
}
} );
}
}
}
} catch( JavaModelException e ) {
_log.error( "findProjectSources: Could not retrieve project sources:", e ); //$NON-NLS-1$
}
}
}
/**
* Does the delta indicate a classpath change?
*
* @param delta
* the delta to inspect
* @return true if classpath has changed
*/
private boolean isClasspathChange( IJavaElementDelta delta ) {
int flags = delta.getFlags();
if( isClasspathChangeFlag( flags ) )
return true;
if( ( flags & IJavaElementDelta.F_CHILDREN ) != 0 ) {
IJavaElementDelta[] children = delta.getAffectedChildren();
for( IJavaElementDelta element : children ) {
if( isClasspathChangeFlag( element.getFlags() ) )
return true;
}
}
return false;
}
protected class BuildTask extends WorkspaceJob {
List< IJavaProject > projectsNeedToBuild;
public BuildTask( List< IJavaProject > javaProjects ) {
super( "" );
projectsNeedToBuild = javaProjects;
}
@Override
public IStatus runInWorkspace( IProgressMonitor monitor ) throws CoreException {
monitor.beginTask( "Building...", projectsNeedToBuild.size() );
for( IJavaProject javaProjects : projectsNeedToBuild ) {
try {
javaProjects.getProject().build( IncrementalProjectBuilder.FULL_BUILD, new SubProgressMonitor( monitor, 1 ) );
} catch( CoreException e ) {
_log.error( e.getMessage() );
}
}
return Status.OK_STATUS;
}
}
}